Skip to content

Commit 08f6169

Browse files
TIHanKevinRansom
authored andcommitted
Span for .. in .. do optimization (#6213)
* Trying to optimize span in for loop * Added Span_GetItem call * Almost success with optimization * Fixed code gen * Added Span optimization tests * Added ReadOnlySpan opt * Cleaning up tests * Moving tests around * Trying to figure out span tests * Trying to fix some tests * Trying to get some more tests passing * Fixed range * Trying to get tests to pass again * Fixing tests for netcore * Fix build * When a solution becomes unloaded, we should clear F#'s cache (#6420) * Changing if directives * Simplifying * Using a type shape for span optimization * Fixing one test * Drastically simplified looking at the type shape for Span * Simplified a bit more * RunScript has expected error messages * Add back net472 * Feedback * Update SpanOptimizationTests.fs * Update SpanOptimizationTests.fs
1 parent 3149b48 commit 08f6169

15 files changed

Lines changed: 571 additions & 78 deletions

File tree

src/fsharp/TastOps.fs

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,11 +1757,14 @@ let isStructRecordOrUnionTyconTy g ty =
17571757
| ValueSome tcref -> tcref.Deref.IsStructRecordOrUnionTycon
17581758
| _ -> false
17591759

1760+
let isStructTyconRef (tcref: TyconRef) =
1761+
let tycon = tcref.Deref
1762+
tycon.IsStructRecordOrUnionTycon || tycon.IsStructOrEnumTycon
1763+
17601764
let isStructTy g ty =
17611765
match tryDestAppTy g ty with
17621766
| ValueSome tcref ->
1763-
let tycon = tcref.Deref
1764-
tycon.IsStructRecordOrUnionTycon || tycon.IsStructOrEnumTycon
1767+
isStructTyconRef tcref
17651768
| _ ->
17661769
isStructAnonRecdTy g ty || isStructTupleTy g ty
17671770

@@ -3014,7 +3017,7 @@ let isByrefLikeTyconRef (g: TcGlobals) m (tcref: TyconRef) =
30143017
| None ->
30153018
let res =
30163019
isByrefTyconRef g tcref ||
3017-
TyconRefHasAttribute g m g.attrib_IsByRefLikeAttribute tcref
3020+
(isStructTyconRef tcref && TyconRefHasAttribute g m g.attrib_IsByRefLikeAttribute tcref)
30183021
tcref.SetIsByRefLike res
30193022
res
30203023

@@ -3023,11 +3026,45 @@ let isSpanLikeTyconRef g m tcref =
30233026
not (isByrefTyconRef g tcref)
30243027

30253028
let isByrefLikeTy g m ty =
3026-
ty |> stripTyEqns g |> (function TType_app(tcref, _) -> isByrefLikeTyconRef g m tcref | _ -> false)
3029+
ty |> stripTyEqns g |> (function TType_app(tcref, _) -> isByrefLikeTyconRef g m tcref | _ -> false)
30273030

30283031
let isSpanLikeTy g m ty =
30293032
isByrefLikeTy g m ty &&
3030-
not (isByrefTy g ty)
3033+
not (isByrefTy g ty)
3034+
3035+
let isSpanTyconRef g m tcref =
3036+
isByrefLikeTyconRef g m tcref &&
3037+
tcref.CompiledRepresentationForNamedType.BasicQualifiedName = "System.Span`1"
3038+
3039+
let isSpanTy g m ty =
3040+
ty |> stripTyEqns g |> (function TType_app(tcref, _) -> isSpanTyconRef g m tcref | _ -> false)
3041+
3042+
let rec tryDestSpanTy g m ty =
3043+
match tryAppTy g ty with
3044+
| ValueSome(tcref, [ty]) when isSpanTyconRef g m tcref -> ValueSome(struct(tcref, ty))
3045+
| _ -> ValueNone
3046+
3047+
let destSpanTy g m ty =
3048+
match tryDestSpanTy g m ty with
3049+
| ValueSome(struct(tcref, ty)) -> struct(tcref, ty)
3050+
| _ -> failwith "destSpanTy"
3051+
3052+
let isReadOnlySpanTyconRef g m tcref =
3053+
isByrefLikeTyconRef g m tcref &&
3054+
tcref.CompiledRepresentationForNamedType.BasicQualifiedName = "System.ReadOnlySpan`1"
3055+
3056+
let isReadOnlySpanTy g m ty =
3057+
ty |> stripTyEqns g |> (function TType_app(tcref, _) -> isReadOnlySpanTyconRef g m tcref | _ -> false)
3058+
3059+
let tryDestReadOnlySpanTy g m ty =
3060+
match tryAppTy g ty with
3061+
| ValueSome(tcref, [ty]) when isReadOnlySpanTyconRef g m tcref -> ValueSome(struct(tcref, ty))
3062+
| _ -> ValueNone
3063+
3064+
let destReadOnlySpanTy g m ty =
3065+
match tryDestReadOnlySpanTy g m ty with
3066+
| ValueSome(struct(tcref, ty)) -> struct(tcref, ty)
3067+
| _ -> failwith "destReadOnlySpanTy"
30313068

30323069
//-------------------------------------------------------------------------
30333070
// List and reference types...

src/fsharp/TastOps.fsi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,6 +2078,18 @@ val isByrefLikeTy : TcGlobals -> range -> TType -> bool
20782078
/// Check if the type is a byref-like but not a byref.
20792079
val isSpanLikeTy : TcGlobals -> range -> TType -> bool
20802080

2081+
val isSpanTy : TcGlobals -> range -> TType -> bool
2082+
2083+
val tryDestSpanTy : TcGlobals -> range -> TType -> struct(TyconRef * TType) voption
2084+
2085+
val destSpanTy : TcGlobals -> range -> TType -> struct(TyconRef * TType)
2086+
2087+
val isReadOnlySpanTy : TcGlobals -> range -> TType -> bool
2088+
2089+
val tryDestReadOnlySpanTy : TcGlobals -> range -> TType -> struct(TyconRef * TType) voption
2090+
2091+
val destReadOnlySpanTy : TcGlobals -> range -> TType -> struct(TyconRef * TType)
2092+
20812093
//-------------------------------------------------------------------------
20822094
// Tuple constructors/destructors
20832095
//-------------------------------------------------------------------------

src/fsharp/TcGlobals.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d
458458
let v_struct_tuple5_tcr = findSysTyconRef sys "ValueTuple`5"
459459
let v_struct_tuple6_tcr = findSysTyconRef sys "ValueTuple`6"
460460
let v_struct_tuple7_tcr = findSysTyconRef sys "ValueTuple`7"
461-
let v_struct_tuple8_tcr = findSysTyconRef sys "ValueTuple`8"
461+
let v_struct_tuple8_tcr = findSysTyconRef sys "ValueTuple`8"
462462

463463
let v_choice2_tcr = mk_MFCore_tcref fslibCcu "Choice`2"
464464
let v_choice3_tcr = mk_MFCore_tcref fslibCcu "Choice`3"
@@ -728,7 +728,7 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d
728728
let v_fail_static_init_info = makeIntrinsicValRef(fslib_MFIntrinsicFunctions_nleref, "FailStaticInit" , None , None , [], ([[v_unit_ty]], v_unit_ty))
729729
let v_check_this_info = makeIntrinsicValRef(fslib_MFIntrinsicFunctions_nleref, "CheckThis" , None , None , [vara], ([[varaTy]], varaTy))
730730
let v_quote_to_linq_lambda_info = makeIntrinsicValRef(fslib_MFLinqRuntimeHelpersQuotationConverter_nleref, "QuotationToLambdaExpression" , None , None , [vara], ([[mkQuotedExprTy varaTy]], mkLinqExpressionTy varaTy))
731-
731+
732732
let tref_DebuggableAttribute = findSysILTypeRef tname_DebuggableAttribute
733733
let tref_CompilerGeneratedAttribute = findSysILTypeRef tname_CompilerGeneratedAttribute
734734

src/fsharp/TypeChecker.fs

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3045,6 +3045,24 @@ let BuildPossiblyConditionalMethodCall cenv env isMutable m isProp minfo valUseF
30453045
let TryFindIntrinsicOrExtensionMethInfo collectionSettings (cenv: cenv) (env: TcEnv) m ad nm ty =
30463046
AllMethInfosOfTypeInScope collectionSettings cenv.infoReader env.NameEnv (Some nm) ad IgnoreOverrides m ty
30473047

3048+
let TryFindFSharpSignatureInstanceGetterProperty (cenv: cenv) (env: TcEnv) m nm ty (sigTys: TType list) =
3049+
TryFindPropInfo cenv.infoReader m env.AccessRights nm ty
3050+
|> List.tryFind (fun propInfo ->
3051+
not propInfo.IsStatic && propInfo.HasGetter &&
3052+
(
3053+
match propInfo.GetterMethod.GetParamTypes(cenv.amap, m, []) with
3054+
| [] -> false
3055+
| argTysList ->
3056+
3057+
let argTys = (argTysList |> List.reduce (@)) @ [ propInfo.GetterMethod.GetFSharpReturnTy(cenv.amap, m, []) ] in
3058+
if argTys.Length <> sigTys.Length then
3059+
false
3060+
else
3061+
(argTys, sigTys)
3062+
||> List.forall2 (typeEquiv cenv.g)
3063+
)
3064+
)
3065+
30483066
/// Build the 'test and dispose' part of a 'use' statement
30493067
let BuildDisposableCleanup cenv env m (v: Val) =
30503068
v.SetHasBeenReferenced()
@@ -7117,6 +7135,25 @@ and TcAnonRecdExpr cenv overallTy env tpenv (isStruct, optOrigExpr, unsortedArgs
71177135

71187136

71197137
and TcForEachExpr cenv overallTy env tpenv (pat, enumSynExpr, bodySynExpr, mWholeExpr, spForLoop) =
7138+
let tryGetOptimizeSpanMethodsAux g m ty isReadOnlySpan =
7139+
match (if isReadOnlySpan then tryDestReadOnlySpanTy g m ty else tryDestSpanTy g m ty) with
7140+
| ValueSome(struct(_, destTy)) ->
7141+
match TryFindFSharpSignatureInstanceGetterProperty cenv env m "Item" ty [ g.int32_ty; (if isReadOnlySpan then mkInByrefTy g destTy else mkByrefTy g destTy) ],
7142+
TryFindFSharpSignatureInstanceGetterProperty cenv env m "Length" ty [ g.int32_ty ] with
7143+
| Some(itemPropInfo), Some(lengthPropInfo) ->
7144+
ValueSome(struct(itemPropInfo.GetterMethod, lengthPropInfo.GetterMethod, isReadOnlySpan))
7145+
| _ ->
7146+
ValueNone
7147+
| _ ->
7148+
ValueNone
7149+
7150+
let tryGetOptimizeSpanMethods g m ty =
7151+
let result = tryGetOptimizeSpanMethodsAux g m ty false
7152+
if result.IsSome then
7153+
result
7154+
else
7155+
tryGetOptimizeSpanMethodsAux g m ty true
7156+
71207157
UnifyTypes cenv env mWholeExpr overallTy cenv.g.unit_ty
71217158

71227159
let mPat = pat.Range
@@ -7141,7 +7178,7 @@ and TcForEachExpr cenv overallTy env tpenv (pat, enumSynExpr, bodySynExpr, mWhol
71417178
let arrVar, arrExpr = mkCompGenLocal mEnumExpr "arr" enumExprTy
71427179
let idxVar, idxExpr = mkCompGenLocal mPat "idx" cenv.g.int32_ty
71437180
let elemTy = destArrayTy cenv.g enumExprTy
7144-
7181+
71457182
// Evaluate the array index lookup
71467183
let bodyExprFixup elemVar bodyExpr = mkCompGenLet mForLoopStart elemVar (mkLdelem cenv.g mForLoopStart elemTy arrExpr idxExpr) bodyExpr
71477184

@@ -7150,13 +7187,37 @@ and TcForEachExpr cenv overallTy env tpenv (pat, enumSynExpr, bodySynExpr, mWhol
71507187

71517188
// Ask for a loop over integers for the given range
71527189
(elemTy, bodyExprFixup, overallExprFixup, Choice2Of3 (idxVar, mkZero cenv.g mForLoopStart, mkDecr cenv.g mForLoopStart (mkLdlen cenv.g mForLoopStart arrExpr)))
7153-
7190+
71547191
| _ ->
7192+
// try optimize 'for i in span do' for span or readonlyspan
7193+
match tryGetOptimizeSpanMethods cenv.g mWholeExpr enumExprTy with
7194+
| ValueSome(struct(getItemMethInfo, getLengthMethInfo, isReadOnlySpan)) ->
7195+
let tcVal = LightweightTcValForUsingInBuildMethodCall cenv.g
7196+
let spanVar, spanExpr = mkCompGenLocal mEnumExpr "span" enumExprTy
7197+
let idxVar, idxExpr = mkCompGenLocal mPat "idx" cenv.g.int32_ty
7198+
let struct(_, elemTy) = if isReadOnlySpan then destReadOnlySpanTy cenv.g mWholeExpr enumExprTy else destSpanTy cenv.g mWholeExpr enumExprTy
7199+
let elemAddrTy = if isReadOnlySpan then mkInByrefTy cenv.g elemTy else mkByrefTy cenv.g elemTy
7200+
7201+
// Evaluate the span index lookup
7202+
let bodyExprFixup elemVar bodyExpr =
7203+
let elemAddrVar, _ = mkCompGenLocal mForLoopStart "addr" elemAddrTy
7204+
let e = mkCompGenLet mForLoopStart elemVar (mkAddrGet mForLoopStart (mkLocalValRef elemAddrVar)) bodyExpr
7205+
let getItemCallExpr, _ = BuildMethodCall tcVal cenv.g cenv.amap PossiblyMutates mWholeExpr true getItemMethInfo ValUseFlag.NormalValUse [] [ spanExpr ] [ idxExpr ]
7206+
mkCompGenLet mForLoopStart elemAddrVar getItemCallExpr e
7207+
7208+
// Evaluate the span expression once and put it in spanVar
7209+
let overallExprFixup overallExpr = mkCompGenLet mForLoopStart spanVar enumExpr overallExpr
7210+
7211+
let getLengthCallExpr, _ = BuildMethodCall tcVal cenv.g cenv.amap PossiblyMutates mWholeExpr true getLengthMethInfo ValUseFlag.NormalValUse [] [ spanExpr ] []
7212+
7213+
// Ask for a loop over integers for the given range
7214+
(elemTy, bodyExprFixup, overallExprFixup, Choice2Of3 (idxVar, mkZero cenv.g mForLoopStart, mkDecr cenv.g mForLoopStart getLengthCallExpr))
71557215

7156-
let enumerableVar, enumerableExprInVar = mkCompGenLocal mEnumExpr "inputSequence" enumExprTy
7157-
let enumeratorVar, enumeratorExpr, _, enumElemTy, getEnumExpr, getEnumTy, guardExpr, _, currentExpr =
7158-
AnalyzeArbitraryExprAsEnumerable cenv env true mEnumExpr enumExprTy enumerableExprInVar
7159-
(enumElemTy, (fun _ x -> x), id, Choice3Of3(enumerableVar, enumeratorVar, enumeratorExpr, getEnumExpr, getEnumTy, guardExpr, currentExpr))
7216+
| _ ->
7217+
let enumerableVar, enumerableExprInVar = mkCompGenLocal mEnumExpr "inputSequence" enumExprTy
7218+
let enumeratorVar, enumeratorExpr, _, enumElemTy, getEnumExpr, getEnumTy, guardExpr, _, currentExpr =
7219+
AnalyzeArbitraryExprAsEnumerable cenv env true mEnumExpr enumExprTy enumerableExprInVar
7220+
(enumElemTy, (fun _ x -> x), id, Choice3Of3(enumerableVar, enumeratorVar, enumeratorExpr, getEnumExpr, getEnumTy, guardExpr, currentExpr))
71607221

71617222
let pat, _, vspecs, envinner, tpenv = TcMatchPattern cenv enumElemTy env tpenv (pat, None)
71627223
let elemVar, pat =

tests/FSharp.Compiler.UnitTests/FSharp.Compiler.UnitTests.fsproj

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@
1717
<Compile Include="HashIfExpression.fs" />
1818
<Compile Include="ProductVersion.fs" />
1919
<Compile Include="EditDistance.fs" />
20-
<Compile Include="Compiler.fs" />
21-
<Compile Include="ILHelpers.fs" />
22-
<Compile Include="Language\AnonRecords.fs" />
23-
<Compile Include="Language\StringConcat.fs" />
24-
<Compile Include="SourceTextTests.fs" />
2520
</ItemGroup>
2621

2722
<ItemGroup>

tests/FSharp.Compiler.UnitTests/Compiler.fs renamed to tests/fsharp/Compiler/CompilerAssert.fs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,41 @@
33
namespace FSharp.Compiler.UnitTests
44

55
open System
6+
open System.IO
7+
open System.Text
8+
open System.Diagnostics
69
open FSharp.Compiler.Text
710
open FSharp.Compiler.SourceCodeServices
11+
open FSharp.Compiler.Interactive.Shell
812

913
open NUnit.Framework
1014

1115
[<RequireQualifiedAccess>]
12-
module Compiler =
16+
module CompilerAssert =
1317

1418
let checker = FSharpChecker.Create()
1519

20+
let private config = TestFramework.initializeSuite ()
21+
1622
let private defaultProjectOptions =
1723
{
1824
ProjectFileName = "Z:\\test.fsproj"
1925
ProjectId = None
2026
SourceFiles = [|"test.fs"|]
27+
#if !NETCOREAPP
2128
OtherOptions = [||]
29+
#else
30+
OtherOptions =
31+
// Hack: Currently a hack to get the runtime assemblies for netcore in order to compile.
32+
let assemblies =
33+
typeof<obj>.Assembly.Location
34+
|> Path.GetDirectoryName
35+
|> Directory.EnumerateFiles
36+
|> Seq.toArray
37+
|> Array.filter (fun x -> x.ToLowerInvariant().Contains("system."))
38+
|> Array.map (fun x -> sprintf "-r:%s" x)
39+
Array.append [|"--targetprofile:netcore"; "--noframework"|] assemblies
40+
#endif
2241
ReferencedProjects = [||]
2342
IsIncompleteTypeCheckEnvironment = false
2443
UseScriptResolutionRules = false
@@ -29,7 +48,7 @@ module Compiler =
2948
Stamp = None
3049
}
3150

32-
let AssertPass (source: string) =
51+
let Pass (source: string) =
3352
let parseResults, fileAnswer = checker.ParseAndCheckFileInProject("test.fs", 0, SourceText.ofString source, defaultProjectOptions) |> Async.RunSynchronously
3453

3554
Assert.True(parseResults.Errors.Length = 0, sprintf "Parse errors: %A" parseResults.Errors)
@@ -40,7 +59,7 @@ module Compiler =
4059

4160
Assert.True(typeCheckResults.Errors.Length = 0, sprintf "Type Check errors: %A" typeCheckResults.Errors)
4261

43-
let AssertSingleErrorTypeCheck (source: string) (expectedErrorNumber: int) (expectedErrorRange: int * int * int * int) (expectedErrorMsg: string) =
62+
let TypeCheckSingleError (source: string) (expectedErrorNumber: int) (expectedErrorRange: int * int * int * int) (expectedErrorMsg: string) =
4463
let parseResults, fileAnswer = checker.ParseAndCheckFileInProject("test.fs", 0, SourceText.ofString source, defaultProjectOptions) |> Async.RunSynchronously
4564

4665
Assert.True(parseResults.Errors.Length = 0, sprintf "Parse errors: %A" parseResults.Errors)
@@ -56,4 +75,40 @@ module Compiler =
5675
Assert.AreEqual(expectedErrorNumber, info.ErrorNumber, "expectedErrorNumber")
5776
Assert.AreEqual(expectedErrorRange, (info.StartLineAlternate, info.StartColumn, info.EndLineAlternate, info.EndColumn), "expectedErrorRange")
5877
Assert.AreEqual(expectedErrorMsg, info.Message, "expectedErrorMsg")
59-
)
78+
)
79+
80+
let RunScript (source: string) (expectedErrorMessages: string list) =
81+
// Intialize output and input streams
82+
use inStream = new StringReader("")
83+
use outStream = new StringWriter()
84+
use errStream = new StringWriter()
85+
86+
// Build command line arguments & start FSI session
87+
let argv = [| "C:\\fsi.exe" |]
88+
#if !NETCOREAPP
89+
let allArgs = Array.append argv [|"--noninteractive"|]
90+
#else
91+
let allArgs = Array.append argv [|"--noninteractive"; "--targetprofile:netcore"|]
92+
#endif
93+
94+
let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration()
95+
use fsiSession = FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, outStream, errStream, collectible = true)
96+
97+
let ch, errors = fsiSession.EvalInteractionNonThrowing source
98+
99+
let errorMessages = ResizeArray()
100+
errors
101+
|> Seq.iter (fun error -> errorMessages.Add(error.Message))
102+
103+
match ch with
104+
| Choice2Of2 ex -> errorMessages.Add(ex.Message)
105+
| _ -> ()
106+
107+
if expectedErrorMessages.Length <> errorMessages.Count then
108+
Assert.Fail(sprintf "Expected error messages: %A \n\n Actual error messages: %A" expectedErrorMessages errorMessages)
109+
else
110+
(expectedErrorMessages, errorMessages)
111+
||> Seq.iter2 (fun expectedErrorMessage errorMessage ->
112+
Assert.AreEqual(expectedErrorMessage, errorMessage)
113+
)
114+

0 commit comments

Comments
 (0)