Skip to content

Commit 3d3fc39

Browse files
Optimize simple range mappings: [for n in start..finish -> f n], &c. (#16832)
1 parent 43b7140 commit 3d3fc39

20 files changed

+5197
-529
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@
4747
* Reverted [#16348](https://github.com/dotnet/fsharp/pull/16348) `ThreadStatic` `CancellationToken` changes to improve test stability and prevent potential unwanted cancellations. ([PR #16536](https://github.com/dotnet/fsharp/pull/16536))
4848
* Refactored parenthesization API. ([PR #16461])(https://github.com/dotnet/fsharp/pull/16461))
4949
* Optimize some interpolated strings by lowering to string concatenation. ([PR #16556](https://github.com/dotnet/fsharp/pull/16556))
50-
* Integral range optimizations. ([PR #16650](https://github.com/dotnet/fsharp/pull/16650))
50+
* Integral range optimizations. ([PR #16650](https://github.com/dotnet/fsharp/pull/16650), [PR #16832](https://github.com/dotnet/fsharp/pull/16832))

docs/release-notes/.Language/preview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
### Added
22

3-
* Lower integral ranges to fast loops in more cases and optimize list and array construction from ranges. ([PR #16650](https://github.com/dotnet/fsharp/pull/16650))
3+
* Lower integral ranges to fast loops in more cases and optimize list and array construction from ranges. ([PR #16650](https://github.com/dotnet/fsharp/pull/16650), [PR #16832](https://github.com/dotnet/fsharp/pull/16832))
44
* Better generic unmanaged structs handling. ([Language suggestion #692](https://github.com/fsharp/fslang-suggestions/issues/692), [PR #12154](https://github.com/dotnet/fsharp/pull/12154))
55
* Bidirectional F#/C# interop for 'unmanaged' constraint. ([PR #12154](https://github.com/dotnet/fsharp/pull/12154))
66
* Make `.Is*` discriminated union properties visible. ([Language suggestion #222](https://github.com/fsharp/fslang-suggestions/issues/222), [PR #16341](https://github.com/dotnet/fsharp/pull/16341))

src/Compiler/CodeGen/IlxGen.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2920,7 +2920,8 @@ and GenExprPreSteps (cenv: cenv) (cgbuf: CodeGenBuffer) eenv expr sequel =
29202920

29212921
let lowering =
29222922
if compileSequenceExpressions then
2923-
LowerComputedCollectionExpressions.LowerComputedListOrArrayExpr cenv.tcVal g cenv.amap expr
2923+
let ilTyForTy ty = GenType cenv expr.Range eenv.tyenv ty
2924+
LowerComputedCollectionExpressions.LowerComputedListOrArrayExpr cenv.tcVal g cenv.amap ilTyForTy expr
29242925
else
29252926
None
29262927

src/Compiler/Optimize/LowerComputedCollections.fs

Lines changed: 118 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ let (|SeqToArray|_|) g expr =
258258

259259
module List =
260260
/// Makes an expression that will build a list from an integral range.
261-
let mkFromIntegralRange tcVal (g: TcGlobals) amap m overallElemTy overallSeqExpr start step finish =
261+
let mkFromIntegralRange tcVal (g: TcGlobals) amap m rangeTy overallElemTy rangeExpr start step finish body =
262262
let collectorTy = g.mk_ListCollector_ty overallElemTy
263263

264264
/// let collector = ListCollector () in
@@ -267,15 +267,24 @@ module List =
267267
let mkListInit mkLoop =
268268
mkCompGenLetMutableIn m "collector" collectorTy (mkDefault (m, collectorTy)) (fun (_, collector) ->
269269
let reader = InfoReader (g, amap)
270-
let loop = mkLoop (fun _idxVar loopVar -> mkCallCollectorAdd tcVal g reader m collector loopVar)
270+
271+
let loop =
272+
mkLoop (fun _idxVar loopVar ->
273+
let body =
274+
body
275+
|> Option.map (fun (loopVal, body) -> mkInvisibleLet m loopVal loopVar body)
276+
|> Option.defaultValue loopVar
277+
278+
mkCallCollectorAdd tcVal g reader m collector body)
279+
271280
let close = mkCallCollectorClose tcVal g reader m collector
272281
mkSequential m loop close
273282
)
274283

275284
mkOptimizedRangeLoop
276285
g
277286
(m, m, m, DebugPointAtWhile.No)
278-
(overallElemTy, overallSeqExpr)
287+
(rangeTy, rangeExpr)
279288
(start, step, finish)
280289
(fun count mkLoop ->
281290
match count with
@@ -301,7 +310,7 @@ module Array =
301310
| NoCheckOvf
302311

303312
/// Makes an expression that will build an array from an integral range.
304-
let mkFromIntegralRange g m overallElemTy overallSeqExpr start step finish =
313+
let mkFromIntegralRange g m rangeTy ilTy overallElemTy rangeExpr start step finish body =
305314
let arrayTy = mkArrayType g overallElemTy
306315

307316
let convToNativeInt ovf expr =
@@ -324,21 +333,21 @@ module Array =
324333
else
325334
expr
326335

327-
let ilTy, ilBasicTy =
328-
let ty = stripMeasuresFromTy g overallElemTy
329-
330-
if typeEquiv g ty g.int32_ty then g.ilg.typ_Int32, DT_I4
331-
elif typeEquiv g ty g.int64_ty then g.ilg.typ_Int64, DT_I8
332-
elif typeEquiv g ty g.uint64_ty then g.ilg.typ_UInt64, DT_U8
333-
elif typeEquiv g ty g.uint32_ty then g.ilg.typ_UInt32, DT_U4
334-
elif typeEquiv g ty g.nativeint_ty then g.ilg.typ_IntPtr, DT_I
335-
elif typeEquiv g ty g.unativeint_ty then g.ilg.typ_UIntPtr, DT_U
336-
elif typeEquiv g ty g.int16_ty then g.ilg.typ_Int16, DT_I2
337-
elif typeEquiv g ty g.uint16_ty then g.ilg.typ_UInt16, DT_U2
338-
elif typeEquiv g ty g.sbyte_ty then g.ilg.typ_SByte, DT_I1
339-
elif typeEquiv g ty g.byte_ty then g.ilg.typ_Byte, DT_U1
340-
elif typeEquiv g ty g.char_ty then g.ilg.typ_Char, DT_U2
341-
else error (InternalError ($"Unable to find IL type for integral type '{overallElemTy}'.", m))
336+
let stelem =
337+
if ilTy = g.ilg.typ_Int32 then I_stelem DT_I4
338+
elif ilTy = g.ilg.typ_Int64 then I_stelem DT_I8
339+
elif ilTy = g.ilg.typ_UInt64 then I_stelem DT_U8
340+
elif ilTy = g.ilg.typ_UInt32 then I_stelem DT_U4
341+
elif ilTy = g.ilg.typ_IntPtr then I_stelem DT_I
342+
elif ilTy = g.ilg.typ_UIntPtr then I_stelem DT_U
343+
elif ilTy = g.ilg.typ_Int16 then I_stelem DT_I2
344+
elif ilTy = g.ilg.typ_UInt16 then I_stelem DT_U2
345+
elif ilTy = g.ilg.typ_SByte then I_stelem DT_I1
346+
elif ilTy = g.ilg.typ_Byte then I_stelem DT_U1
347+
elif ilTy = g.ilg.typ_Char then I_stelem DT_U2
348+
elif ilTy = g.ilg.typ_Double then I_stelem DT_R8
349+
elif ilTy = g.ilg.typ_Single then I_stelem DT_R4
350+
else I_stelem_any (ILArrayShape.SingleDimensional, ilTy)
342351

343352
/// (# "newarr !0" type ('T) count : 'T array #)
344353
let mkNewArray count =
@@ -356,13 +365,21 @@ module Array =
356365
/// array
357366
let mkArrayInit count mkLoop =
358367
mkCompGenLetIn m "array" arrayTy (mkNewArray count) (fun (_, array) ->
359-
let loop = mkLoop (fun idxVar loopVar -> mkAsmExpr ([I_stelem ilBasicTy], [], [array; convToNativeInt NoCheckOvf idxVar; loopVar], [], m))
368+
let loop =
369+
mkLoop (fun idxVar loopVar ->
370+
let body =
371+
body
372+
|> Option.map (fun (loopVal, body) -> mkInvisibleLet m loopVal loopVar body)
373+
|> Option.defaultValue loopVar
374+
375+
mkAsmExpr ([stelem], [], [array; convToNativeInt NoCheckOvf idxVar; body], [], m))
376+
360377
mkSequential m loop array)
361378

362379
mkOptimizedRangeLoop
363380
g
364381
(m, m, m, DebugPointAtWhile.No)
365-
(overallElemTy, overallSeqExpr)
382+
(rangeTy, rangeExpr)
366383
(start, step, finish)
367384
(fun count mkLoop ->
368385
match count with
@@ -399,7 +416,64 @@ module Array =
399416
)
400417
)
401418

402-
let LowerComputedListOrArrayExpr tcVal (g: TcGlobals) amap overallExpr =
419+
/// f (); …; Seq.singleton x
420+
///
421+
/// E.g., in [for x in … do f (); …; yield x]
422+
[<return: Struct>]
423+
let (|SimpleSequential|_|) g expr =
424+
let rec loop expr cont =
425+
match expr with
426+
| Expr.Sequential (expr1, DebugPoints (ValApp g g.seq_singleton_vref (_, [body], _), debug), kind, m) ->
427+
ValueSome (cont (expr1, debug body, kind, m))
428+
429+
| Expr.Sequential (expr1, body, kind, m) ->
430+
loop body (cont >> fun body -> Expr.Sequential (expr1, body, kind, m))
431+
432+
| _ -> ValueNone
433+
434+
loop expr Expr.Sequential
435+
436+
/// The representation used for
437+
///
438+
/// for … in … -> …
439+
///
440+
/// and
441+
///
442+
/// for … in … do yield …
443+
[<return: Struct>]
444+
let (|SeqMap|_|) g expr =
445+
match expr with
446+
| ValApp g g.seq_map_vref ([ty1; ty2], [Expr.Lambda (valParams = [loopVal]; bodyExpr = body) as mapping; input], _) ->
447+
ValueSome (ty1, ty2, input, mapping, loopVal, body)
448+
| _ -> ValueNone
449+
450+
/// The representation used for
451+
///
452+
/// for … in … do f (); …; yield …
453+
[<return: Struct>]
454+
let (|SeqCollectSingle|_|) g expr =
455+
match expr with
456+
| ValApp g g.seq_collect_vref ([ty1; _; ty2], [Expr.Lambda (valParams = [loopVal]; bodyExpr = SimpleSequential g body) as mapping; input], _) ->
457+
ValueSome (ty1, ty2, input, mapping, loopVal, body)
458+
| _ -> ValueNone
459+
460+
/// for … in … -> …
461+
/// for … in … do yield …
462+
/// for … in … do f (); …; yield …
463+
[<return: Struct>]
464+
let (|SimpleMapping|_|) g expr =
465+
match expr with
466+
// for … in … -> …
467+
// for … in … do yield …
468+
| ValApp g g.seq_delay_vref (_, [Expr.Lambda (bodyExpr = SeqMap g (ty1, ty2, input, mapping, loopVal, body))], _)
469+
470+
// for … in … do f (); …; yield …
471+
| ValApp g g.seq_delay_vref (_, [Expr.Lambda (bodyExpr = SeqCollectSingle g (ty1, ty2, input, mapping, loopVal, body))], _) ->
472+
ValueSome (ty1, ty2, input, mapping, loopVal, body)
473+
474+
| _ -> ValueNone
475+
476+
let LowerComputedListOrArrayExpr tcVal (g: TcGlobals) amap ilTyForTy overallExpr =
403477
// If ListCollector is in FSharp.Core then this optimization kicks in
404478
if g.ListCollector_tcr.CanDeref then
405479
match overallExpr with
@@ -408,8 +482,17 @@ let LowerComputedListOrArrayExpr tcVal (g: TcGlobals) amap overallExpr =
408482
match overallSeqExpr with
409483
// [start..finish]
410484
// [start..step..finish]
411-
| IntegralRange g (_, (start, step, finish)) when g.langVersion.SupportsFeature LanguageFeature.LowerIntegralRangesToFastLoops ->
412-
Some (List.mkFromIntegralRange tcVal g amap m overallElemTy overallSeqExpr start step finish)
485+
| IntegralRange g (rangeTy, (start, step, finish)) when
486+
g.langVersion.SupportsFeature LanguageFeature.LowerIntegralRangesToFastLoops
487+
->
488+
Some (List.mkFromIntegralRange tcVal g amap m rangeTy overallElemTy overallSeqExpr start step finish None)
489+
490+
// [for … in start..finish -> …]
491+
// [for … in start..step..finish -> …]
492+
| SimpleMapping g (_, _, rangeExpr & IntegralRange g (rangeTy, (start, step, finish)), _, loopVal, body) when
493+
g.langVersion.SupportsFeature LanguageFeature.LowerIntegralRangesToFastLoops
494+
->
495+
Some (List.mkFromIntegralRange tcVal g amap m rangeTy overallElemTy rangeExpr start step finish (Some (loopVal, body)))
413496

414497
// [(* Anything more complex. *)]
415498
| _ ->
@@ -421,8 +504,17 @@ let LowerComputedListOrArrayExpr tcVal (g: TcGlobals) amap overallExpr =
421504
match overallSeqExpr with
422505
// [|start..finish|]
423506
// [|start..step..finish|]
424-
| IntegralRange g (_, (start, step, finish)) when g.langVersion.SupportsFeature LanguageFeature.LowerIntegralRangesToFastLoops ->
425-
Some (Array.mkFromIntegralRange g m overallElemTy overallSeqExpr start step finish)
507+
| IntegralRange g (rangeTy, (start, step, finish)) when
508+
g.langVersion.SupportsFeature LanguageFeature.LowerIntegralRangesToFastLoops
509+
->
510+
Some (Array.mkFromIntegralRange g m rangeTy (ilTyForTy overallElemTy) overallElemTy overallSeqExpr start step finish None)
511+
512+
// [|for … in start..finish -> …|]
513+
// [|for … in start..step..finish -> …|]
514+
| SimpleMapping g (_, _, rangeExpr & IntegralRange g (rangeTy, (start, step, finish)), _, loopVal, body) when
515+
g.langVersion.SupportsFeature LanguageFeature.LowerIntegralRangesToFastLoops
516+
->
517+
Some (Array.mkFromIntegralRange g m rangeTy (ilTyForTy overallElemTy) overallElemTy rangeExpr start step finish (Some (loopVal, body)))
426518

427519
// [|(* Anything more complex. *)|]
428520
| _ ->

src/Compiler/Optimize/LowerComputedCollections.fsi

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22

33
module internal FSharp.Compiler.LowerComputedCollectionExpressions
44

5+
open FSharp.Compiler.AbstractIL.IL
56
open FSharp.Compiler.Import
67
open FSharp.Compiler.TcGlobals
78
open FSharp.Compiler.TypedTree
89

910
val LowerComputedListOrArrayExpr:
10-
tcVal: ConstraintSolver.TcValF -> g: TcGlobals -> amap: ImportMap -> Expr -> Expr option
11+
tcVal: ConstraintSolver.TcValF ->
12+
g: TcGlobals ->
13+
amap: ImportMap ->
14+
ilTyForTy: (TType -> ILType) ->
15+
overallExpr: Expr ->
16+
Expr option

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,13 @@ module ComputedCollections =
3434
let ``UInt64RangeLists_fs`` compilation =
3535
compilation
3636
|> verifyCompilation
37+
38+
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"ForNInRangeArrays.fs"|])>]
39+
let ``ForNInRangeArrays_fs`` compilation =
40+
compilation
41+
|> verifyCompilation
42+
43+
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"ForNInRangeLists.fs"|])>]
44+
let ``ForNInRangeLists_fs`` compilation =
45+
compilation
46+
|> verifyCompilation
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
let f0 f = [|for n in 1..10 do f (); yield n|]
2+
let f00 f g = [|for n in 1..10 do f (); g (); yield n|]
3+
let f000 f = [|for n in 1..10 do f (); yield n; yield n + 1|]
4+
let f0000 () = [|for n in 1..10 do yield n|]
5+
let f1 () = [|for n in 1..10 -> n|]
6+
let f2 () = [|for n in 10..1 -> n|]
7+
let f3 () = [|for n in 1..1..10 -> n|]
8+
let f4 () = [|for n in 1..2..10 -> n|]
9+
let f5 () = [|for n in 10..1..1 -> n|]
10+
let f6 () = [|for n in 1..-1..10 -> n|]
11+
let f7 () = [|for n in 10..-1..1 -> n|]
12+
let f8 () = [|for n in 10..-2..1 -> n|]
13+
let f9 start = [|for n in start..10 -> n|]
14+
let f10 finish = [|for n in 1..finish -> n|]
15+
let f11 start finish = [|for n in start..finish -> n|]
16+
let f12 start = [|for n in start..1..10 -> n|]
17+
let f13 step = [|for n in 1..step..10 -> n|]
18+
let f14 finish = [|for n in 1..1..finish -> n|]
19+
let f15 start step = [|for n in start..step..10 -> n|]
20+
let f16 start finish = [|for n in start..1..finish -> n|]
21+
let f17 step finish = [|for n in 1..step..finish -> n|]
22+
let f18 start step finish = [|for n in start..step..finish -> n|]
23+
let f19 f = [|for n in f ()..10 -> n|]
24+
let f20 f = [|for n in 1..f () -> n|]
25+
let f21 f g = [|for n in f ()..g() -> n|]
26+
let f22 f = [|for n in f ()..1..10 -> n|]
27+
let f23 f = [|for n in 1..f ()..10 -> n|]
28+
let f24 f = [|for n in 1..1..f () -> n|]
29+
let f25 f g h = [|for n in f ()..g ()..h () -> n|]
30+
let f26 start step finish = [|for n in start..step..finish -> n, float n|]
31+
let f27 start step finish = [|for n in start..step..finish -> struct (n, float n)|]
32+
let f28 start step finish = [|for n in start..step..finish -> let x = n + 1 in n * n|]

0 commit comments

Comments
 (0)