Repository navigation
Expand file tree
/
Copy pathScalar.lean
More file actions
3642 lines (3475 loc) · 207 KB
/
Copy pathScalar.lean
File metadata and controls
3642 lines (3475 loc) · 207 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import Lean
import LeanExe.IR.ArrayLiteral
import LeanExe.IR.Fold
import LeanExe.IR.ListFold
import LeanExe.IR.Record
import LeanExe.IR.Release
import LeanExe.IR.Function
import LeanExe.IR.Loop
import LeanExe.IR.Append
import LeanExe.IR.Build
import LeanExe.IR.BuildRecord
import LeanExe.IR.Update
import LeanExe.IR.ArrayLoop
import LeanExe.IR.RepeatWhile
import LeanExe.IR.Hint
import LeanExe.IR.Copy
import LeanExe.Dialect.Float32
namespace LeanExe.Compiler
open Lean Meta LeanExe.IR
abbrev IRExpr := LeanExe.IR.Expr
/-- The source operators on `UInt64` the compiler translates, with the IR
operation and the rule name for each. Instance arguments are not checked: a
wrong match makes the proof fail. -/
def binaryRules : List (Name × U64Op × String) :=
[(``HAdd.hAdd, .add, "add"), (``HSub.hSub, .sub, "sub"), (``HMul.hMul, .mul, "mul"),
(``HDiv.hDiv, .divU, "div"), (``HMod.hMod, .remU, "mod"),
(``HAnd.hAnd, .bitAnd, "and"), (``HOr.hOr, .bitOr, "or"), (``HXor.hXor, .bitXor, "xor"),
(``HShiftLeft.hShiftLeft, .shiftLeft, "shiftLeft"),
(``HShiftRight.hShiftRight, .shiftRight, "shiftRight")]
def isUInt64 (type : Lean.Expr) : MetaM Bool := do
return (← whnfR type).isConstOf ``UInt64
def isFloat (type : Lean.Expr) : MetaM Bool := do
return (← whnfR type).isConstOf ``Float
def isFloat32 (type : Lean.Expr) : MetaM Bool := do
return (← whnfR type).isConstOf ``Float32
/-- A source floating-point format: `Float`, binary64, or `Float32`, binary32. -/
inductive FloatFormat where
| binary64
| binary32
deriving BEq
/-- The format of `type`, or `none` when it is neither `Float` nor `Float32`. -/
def floatFormat? (type : Lean.Expr) : MetaM (Option FloatFormat) := do
let type ← whnfR type
if type.isConstOf ``Float then return some .binary64
if type.isConstOf ``Float32 then return some .binary32
return none
namespace FloatFormat
def type : FloatFormat → ScalarType
| .binary64 => .f64
| .binary32 => .f32
/-- The hint rule `name` for the format: `float name` or `float32 name`. -/
def rule (fmt : FloatFormat) (name : String) : String :=
match fmt with
| .binary64 => s!"float {name}"
| .binary32 => s!"float32 {name}"
def get : (fmt : FloatFormat) → Nat → IRExpr fmt.type
| .binary64, index => .getF index
| .binary32, index => .getF32 index
/-- The literal `mantissa * 10 ^ (±exponent)` rounded to the format. -/
def literal : (fmt : FloatFormat) → Nat → Bool → Nat → IRExpr fmt.type
| .binary64, m, negative, e => .constF (OfScientific.ofScientific m negative e : Float).toBits
| .binary32, m, negative, e =>
.constF32 (OfScientific.ofScientific m negative e : Float32).toBits
def natLiteral : (fmt : FloatFormat) → Nat → IRExpr fmt.type
| .binary64, n => .constF (Float.ofNat n).toBits
| .binary32, n => .constF32 (Float32.ofNat n).toBits
/-- Negative zero, from which `-x` subtracts `x`. -/
def negZero : (fmt : FloatFormat) → IRExpr fmt.type
| .binary64 => .constF 0x8000000000000000
| .binary32 => .constF32 0x80000000
def bin : (fmt : FloatFormat) → F64Op → IRExpr fmt.type → IRExpr fmt.type → IRExpr fmt.type
| .binary64, op, l, r => .binF op l r
| .binary32, op, l, r =>
.binF32 (match op with | .add => .add | .sub => .sub | .mul => .mul | .div => .div) l r
def sqrt : (fmt : FloatFormat) → IRExpr fmt.type → IRExpr fmt.type
| .binary64, x => .unF .sqrt x
| .binary32, x => .unF32 .sqrt x
def abs : (fmt : FloatFormat) → IRExpr fmt.type → IRExpr fmt.type
| .binary64, x => .unF .abs x
| .binary32, x => .unF32 .abs x
def ite : (fmt : FloatFormat) → IRExpr .bool → IRExpr fmt.type → IRExpr fmt.type → IRExpr fmt.type
| .binary64, c, a, b => .iteF c a b
| .binary32, c, a, b => .iteF32 c a b
def eq : (fmt : FloatFormat) → IRExpr fmt.type → IRExpr fmt.type → IRExpr .bool
| .binary64, l, r => .eqF l r
| .binary32, l, r => .eqF32 l r
def lt : (fmt : FloatFormat) → IRExpr fmt.type → IRExpr fmt.type → IRExpr .bool
| .binary64, l, r => .ltF l r
| .binary32, l, r => .ltF32 l r
def le : (fmt : FloatFormat) → IRExpr fmt.type → IRExpr fmt.type → IRExpr .bool
| .binary64, l, r => .leF l r
| .binary32, l, r => .leF32 l r
/-- Element `position` of the array at local `array`, a word holding the element's bits. -/
def read : (fmt : FloatFormat) → Nat → IRExpr .u64 → IRExpr fmt.type
| .binary64, array, position => .ofBits (.read array position)
| .binary32, array, position => .ofBits32 (.read array position)
end FloatFormat
/-- The source operators on floats the compiler translates, with their hint rule names. -/
def floatRules : List (Name × F64Op × String) :=
[(``HAdd.hAdd, .add, "add"), (``HSub.hSub, .sub, "sub"), (``HMul.hMul, .mul, "mul"),
(``HDiv.hDiv, .div, "div")]
def isUInt64Array (type : Lean.Expr) : MetaM Bool := do
let type ← whnfR type
if type.isAppOfArity ``Array 1 then isUInt64 type.appArg! else return false
/-- Whether `type` is `Array Float` or `Array Float32`, both stored as arrays of words. -/
def isFloatArray (type : Lean.Expr) : MetaM Bool := do
let type ← whnfR type
if type.isAppOfArity ``Array 1 then
return (← isFloat type.appArg!) || (← isFloat32 type.appArg!)
else return false
def isUInt64List (type : Lean.Expr) : MetaM Bool := do
let type ← whnfR type
if type.isAppOfArity ``List 1 then isUInt64 type.appArg! else return false
/-- The number of instructions in the code of an expression or a statement. It
does not depend on the scratch index. -/
def exprLength (e : IRExpr type) : Nat := (e.program 0).length
def stmtLength (s : LeanExe.IR.Stmt) : Nat := (s.program 0).length
/-- The most values a recursive internal function may hold in its frame: parameters, locals,
and scratch. With the depth limit of 1,000, every accepted frame overflows Wasmtime's default
512 KiB stack only beyond 2,000 calls, measured on aarch64 with Wasmtime 44. -/
def recursiveFrameLimit : Nat := 24
/-- The functions that `s` calls. -/
def callsOf : LeanExe.IR.Stmt → List Nat
| .seq first second => callsOf first ++ callsOf second
| .ite _ thenStmt elseStmt => callsOf thenStmt ++ callsOf elseStmt
| .while _ body => callsOf body
| .call func _ _ => [func]
| _ => []
/-- Whether a recursive body may call `func`, which carries no depth: it calls only the
runtime's `alloc` and `release` and holds at most `recursiveFrameLimit` values, so it adds one
bounded frame on top of the recursion. -/
def isLeaf (func : LeanExe.IR.Func) : Bool :=
(callsOf func.body).all (· < 2) &&
func.params.length + func.vars.length + func.width ≤ recursiveFrameLimit
/-- The compiler's view of the definition being compiled. `words`, `floats`,
`arrays`, `floatArrays`, and `lists` give the local of each `UInt64`, `Float`,
`Array UInt64`, `Array Float`, and `List UInt64` variable in scope, `tuples` gives the
locals of each pair-valued variable's components, and `callees` gives the
function index of each definition compiled into the same module. A recursive definition's
locals are the parameters, then `result`, `done`, and one temporary per
parameter. `foldable` says whether a fold may appear: a fold runs
before the value that contains it, so it may not appear in a branch, in a fold
body, or in a recursive definition. -/
structure Ctx where
self : Name
params : Array Lean.Expr
words : List (Lean.Expr × Nat)
floats : List (Lean.Expr × Nat)
arrays : List (Lean.Expr × Nat)
floatArrays : List (Lean.Expr × Nat)
/-- Each array of records in scope: its local and the components of one element, which the
array stores in order, one word each. -/
recordArrays : List (Lean.Expr × Nat × List ScalarType) := []
lists : List (Lean.Expr × Nat) := []
/-- The local of each variable of a recursive user type in scope, a borrowed pointer. -/
nodes : List (Lean.Expr × Nat) := []
/-- In the internal function of a recursive definition that is not tail recursive: the
function's index and the local of its depth parameter. -/
selfCall : Option (Nat × Nat) := none
tuples : List (Lean.Expr × List (Nat × ScalarType)) := []
callees : List (Name × Nat) := []
/-- The internal function of each recursive callee, which a call from a recursive body
enters at the caller's depth plus one. -/
internals : List (Name × Nat) := []
/-- The non-recursive callees that a recursive body may call: those that call no compiled
function and hold at most `recursiveFrameLimit` values in their frame. -/
leaves : List Name := []
foldable : Bool
/-- Whether code may allocate or call: false inside an element of
`LeanExe.build`, whose statements must keep the store. -/
allocating : Bool := true
/-- Whether calls with scalar arguments and results may appear where code must
keep the store: true in loop bodies and elements of `LeanExe.build`. -/
pureCalls : Bool := false
/-- Whether `peel` binds a `let` of a record by pushing its statements to the prelude. A
translation that builds its statement list itself, such as a loop body's, binds such a `let`
in its own list. -/
peelLets : Bool := true
/-- Whether such a call may also lend a value of a recursive type: true in loop bodies. -/
lendsTrees : Bool := false
/-- Whether a `let` may bind an array: true at the top of a function body, false
in a branch, since the array is released at the end of the function. -/
temporaries : Bool := true
/-- The positions of the owned parameters of each definition in `callees`. -/
owners : List (Name × List Nat) := []
/-- The array parameters that this code may move: those whose last use, in the
result term, is the left operand of `++` or an owned parameter of a callee. Also the owned
values of recursive types: parameters that the result moves, and the children of an owned
record that a match examines. -/
owned : List Lean.Expr := []
/-- In the record branch of a match on an owned value of a recursive type: the value, the
local of its pointer, and the field variables. A constructor of the same type rewrites the
record in place. -/
reuse : Option (Lean.Expr × Nat × List Lean.Expr) := none
def Ctx.result (ctx : Ctx) : Nat := ctx.params.size
def Ctx.done (ctx : Ctx) : Nat := ctx.params.size + 1
def Ctx.temp (ctx : Ctx) (i : Nat) : Nat := ctx.params.size + 2 + i
def Ctx.vars (ctx : Ctx) : Nat := ctx.params.size + 2
/-- Where a node's code starts: the path to the enclosing instruction list and
the index in that list. -/
structure Loc where
prefix_ : List Nat
index : Nat
def Loc.path (loc : Loc) : List Nat := loc.prefix_ ++ [loc.index]
def Loc.skip (loc : Loc) (n : Nat) : Loc := { loc with index := loc.index + n }
/-- The start of branch `branch` (0 or 1) of the `if` at `loc`, or of the body
of the `block` or `loop` at `loc` when `branch` is `none`. -/
def Loc.inside (loc : Loc) (branch : Option Nat) : Loc :=
{ prefix_ := loc.path ++ branch.toList, index := 0 }
def mkHint (loc : Loc) (length : Nat) (rule source : String) : Hint :=
{ func := 0, path := loc.path, length, rule, source }
/-- The hint for code moved `offset` instructions later in the function body. -/
def Hint.shift (offset : Nat) (hint : Hint) : Hint :=
match hint.path with
| i :: rest => { hint with path := (i + offset) :: rest }
| [] => hint
/-- The start of the body's code in an array or list fold whose code starts at `loc`:
inside the block and loop of the `while`, after the condition, the exit test, and the
element load. -/
def foldBodyLoc (loc : Loc) : LeanExe.IR.Stmt → Loc
| .seq first (.seq second (.while condition (.seq load _))) =>
(((loc.skip (stmtLength first + stmtLength second)).inside none).inside none).skip
(exprLength condition + 2 + stmtLength load)
| .seq first (.while condition (.seq load _)) =>
(((loc.skip (stmtLength first)).inside none).inside none).skip
(exprLength condition + 2 + stmtLength load)
| _ => loc
def sourceOf (term : Lean.Expr) : MetaM String := return toString (← ppExpr term)
/-- The elements of an array literal `#[e₀, …]`, which elaborates to
`List.toArray [e₀, …]`. -/
def arrayLiteral? (term : Lean.Expr) : Option (List Lean.Expr) :=
match term.consumeMData.getAppFnArgs with
| (``List.toArray, #[_, list]) | (``Array.mk, #[_, list]) => list.consumeMData.listLit?.map (·.2)
| _ => none
/-- Statements that run before the value being translated and become the start
of the function body, with their hints, their code length, and the types of the
compiler's variables, which start at local `base`. -/
structure Prelude where
stmts : Array LeanExe.IR.Stmt := #[]
hints : Array Hint := #[]
length : Nat := 0
base : Nat
vars : Array ScalarType := #[]
names : Array (String × Nat) := #[]
/-- The variables and locals of the temporary arrays, with their sources. The end of the
function releases those that the code has not moved. -/
temporaries : Array (Lean.Expr × Nat × String) := #[]
/-- The owned parameters that the code has moved on the current path. -/
consumed : List Lean.Expr := []
/-- The matched owned values whose records a constructor has rewritten on the current path. -/
rebuilt : List Lean.Expr := []
/-- The arrays that already translated parts of a value read. Those parts run after the
statements of the parts translated now, which therefore may not move these arrays. -/
pendingReads : List Lean.Expr := []
/-- The copy function of each recursive type that the module's code copies, with its index. -/
copies : List (Name × Nat) := []
/-- The index of the next copy function, or `none` outside a module list. -/
nextCopy : Option Nat := none
/-- The next free local. -/
def Prelude.next (p : Prelude) : Nat := p.base + p.vars.size
abbrev CompileM := StateT Prelude MetaM
/-- The user types the compiler accepts: an enumeration, held as the word of its constructor
index; a structure, held as its fields' components in order; and a sum, held as the word of
its constructor index followed by every constructor's fields, in order. -/
inductive UserType where
| enum (ctors : List Name)
| struct (ctor : Name) (fields : List Lean.Expr)
| sum (ctors : List (Name × List Lean.Expr))
| recursive (ctors : List (Name × List Lean.Expr))
/-- `type` as a user type: an inductive type without parameters or indices that is an
enumeration, whose constructors have no fields, a structure, or a sum, or a recursive type
that is neither nested nor mutual, whose values are records on the heap. -/
def userType? (type : Lean.Expr) : MetaM (Option UserType) := do
let .const name _ ← whnfR type | return none
if name == ``UInt64 || name == ``Float || name == ``Float32 || name == ``Nat then return none
let some (.inductInfo info) := (← getEnv).find? name | return none
unless info.numParams == 0 && info.numIndices == 0 do return none
let fields ← info.ctors.mapM fun ctor => do
forallTelescope (← getConstInfoCtor ctor).type fun xs _ => xs.toList.mapM inferType
if info.isRec then
unless info.numNested == 0 && info.all.length == 1 do return none
return some (.recursive (info.ctors.zip fields))
if fields.all (·.isEmpty) then return some (.enum info.ctors)
if let [ctor] := info.ctors then
if isStructure (← getEnv) name then return some (.struct ctor fields[0]!)
return none
return some (.sum (info.ctors.zip fields))
/-- Whether `type` is a recursive user type, whose values are pointers to records. -/
def isNodeType (type : Lean.Expr) : MetaM Bool :=
return (← userType? type) matches some (.recursive _)
/-- Whether `type` is an array of a structure, sum, or enumeration, stored as its elements'
component words. -/
def isRecordArray (type : Lean.Expr) : MetaM Bool := do
let type ← whnfR type
unless type.isAppOfArity ``Array 1 do return false
return (← userType? type.appArg!) matches some (.struct ..) | some (.sum ..) | some (.enum ..)
/-- Whether `type` is `Array UInt64`, `Array Float`, `Array Float32`, or an array of records,
each a pointer word. -/
def isArray (type : Lean.Expr) : MetaM Bool := do
return (← isUInt64Array type) || (← isFloatArray type) || (← isRecordArray type)
/-- Whether `type` is `UInt64` or an enumeration, held as one word. -/
def isWordType (type : Lean.Expr) : MetaM Bool := do
if ← isUInt64 type then return true
return (← userType? type) matches some (.enum _)
/-- Whether `name` is the constructor without fields of a recursive type, the null pointer. -/
def isNullCtor (name : Name) : MetaM Bool := do
let some (.ctorInfo info) := (← getEnv).find? name | return false
unless info.numFields == 0 do return false
return (← userType? (.const info.induct [])) matches some (.recursive _)
/-- The fields of `term`, a list cell `x :: xs` of words or a constructor application with
fields of a recursive type whose other constructor has none, with the child mask of its
record: bit `i` is set when field `i` is a value of a recursive type. A type with more than
one constructor with fields needs a tag, which is not supported yet. -/
def recordCell? (term : Lean.Expr) : MetaM (Option (List Lean.Expr × UInt64)) := do
let term := term.consumeMData
if let (``List.cons, #[element, head, tail]) := term.getAppFnArgs then
unless ← isUInt64 element do return none
return some ([head, tail], 2)
let .const name _ := term.getAppFn | return none
let some (.ctorInfo info) := (← getEnv).find? name | return none
let some (.recursive ctors) ← userType? (.const info.induct []) | return none
let some (_, fields) := ctors.find? (·.1 == name) | return none
unless !fields.isEmpty && term.getAppNumArgs == fields.length do return none
unless ctors.length == 2 && (ctors.filter (!·.2.isEmpty)).length == 1 do
throwError "a recursive type must have one constructor without fields and one with fields: {info.induct}"
let mut mask : Nat := 0
for h : i in [:fields.length] do
if ← isNodeType fields[i] then
mask := mask + 2 ^ i
else unless ← isWordType fields[i] do
throwError "a field of a record built by the compiler must be a word or a value of a recursive type: {fields[i]}"
return some (term.getAppArgs.toList, UInt64.ofNat mask)
/-- Whether `type` is a pair, a structure, or a sum, whose values have several components. -/
def isTupleType (type : Lean.Expr) : MetaM Bool := do
let type ← whnfR type
if type.isAppOfArity ``Prod 2 then return true
match ← userType? type with
| some (.struct ..) | some (.sum _) => return true
| _ => return false
/-- `term` unfolded at its head, when its head is a function marked `@[inline]`: such a
function, when no rule covers it and the module does not compile it, is inlined at its use. -/
def unfoldInline? (term : Lean.Expr) : MetaM (Option Lean.Expr) := do
let .const fn _ := term.getAppFn | return none
unless Lean.Compiler.hasInlineAttribute (← getEnv) fn do return none
return (← unfoldDefinition? term).map (·.headBeta)
/-- Whether every component of `type` is a word or a float: a word, float, or enumeration, or a
pair, structure, or sum of such. -/
partial def isFlatType (type : Lean.Expr) : MetaM Bool := do
let type ← whnfR type
if (← isUInt64 type) || (← isFloat type) || (← isFloat32 type) then return true
if type.isAppOfArity ``Prod 2 then
return (← isFlatType type.appFn!.appArg!) && (← isFlatType type.appArg!)
match ← userType? type with
| some (.enum _) => return true
| some (.struct _ fields) => fields.allM isFlatType
| some (.sum ctors) => ctors.allM fun (_, fields) => fields.allM isFlatType
| _ => return false
/-- The index of the enumeration constructor `name`. -/
def enumIndex? (name : Name) : MetaM (Option Nat) := do
let some (.ctorInfo info) := (← getEnv).find? name | return none
let some (.enum _) ← userType? (.const info.induct []) | return none
return some info.cidx
/-- The scalar type of a `UInt64`, `Float`, `Float32`, or enumeration type. -/
def scalarTypeOf (type : Lean.Expr) : MetaM ScalarType := do
if ← isUInt64 type then return .u64
if ← isFloat type then return .f64
if ← isFloat32 type then return .f32
if (← userType? type) matches some (.enum _) then return .u64
throwError "unsupported type {type}"
/-- The WebAssembly value types of a value of type `type`: a word for `UInt64`, for an
enumeration, and, when `arrays`, for an array or list pointer; a float for `Float`; the components of
each side of a pair and of each field of a structure, in order; and for a sum, a word for the
constructor index followed by the components of every constructor's fields. The fields of a
structure or sum may not be arrays. -/
partial def componentTypes (type : Lean.Expr) (arrays : Bool) : MetaM (List ScalarType) := do
let type ← whnfR type
if type.isAppOfArity ``Prod 2 then
return (← componentTypes type.appFn!.appArg! arrays) ++ (← componentTypes type.appArg! arrays)
if arrays && ((← isArray type) || (← isUInt64List type) || (← isNodeType type)) then
return [.u64]
match ← userType? type with
| some (.struct _ fields) => return (← fields.mapM fun field => componentTypes field false).flatten
| some (.sum ctors) =>
return .u64 :: (← ctors.mapM fun (_, fields) => return (← fields.mapM fun field =>
componentTypes field false).flatten).flatten
| _ => return [← scalarTypeOf type]
/-- The components of a loop state: words and floats. -/
def stateTypes (type : Lean.Expr) : MetaM (List ScalarType) := componentTypes type false
/-- The components of `element` when it is a structure, sum, or enumeration whose components
are words and floats: an array of `element` stores each element's components in order, one word
each. -/
def recordComponents? (element : Lean.Expr) : MetaM (Option (List ScalarType)) := do
match ← userType? element with
| some (.struct ..) | some (.sum ..) | some (.enum ..) =>
return some (← componentTypes element false)
| _ => return none
/-- The components of one element of `type` when it is an array of records, as
`recordComponents?` gives them. -/
def recordArrayComponents? (type : Lean.Expr) : MetaM (Option (List ScalarType)) := do
let type ← whnfR type
unless type.isAppOfArity ``Array 1 do return none
recordComponents? type.appArg!
/-- Whether every component of `term`, of a word, float, enumeration, structure, or sum type
`type`, is the word 0: a word 0, a float `UInt64.toFloat 0`, the first constructor of an
enumeration, or the first constructor of a structure or sum with such fields. -/
partial def isZeroValue (term type : Lean.Expr) : MetaM Bool := do
if ← isUInt64 type then return ← isDefEq term (← mkNumeral type 0)
let zero ← mkNumeral (mkConst ``UInt64) 0
if ← isFloat type then
return ← withTransparency .instances <| isDefEq term (mkApp (mkConst ``UInt64.toFloat) zero)
if ← isFloat32 type then
return ← withTransparency .instances <|
isDefEq term (mkApp (mkConst ``UInt64.toFloat32) zero)
let first ← match ← userType? type with
| some (.enum (ctor :: _)) => return ← isDefEq term (mkConst ctor)
| some (.struct ctor fields) => pure (ctor, fields)
| some (.sum (first :: _)) => pure first
| _ => return false
let (ctor, fields) := first
let term ← whnfD term
unless term.isAppOfArity ctor fields.length do return false
(term.getAppArgs.toList.zip fields).allM fun (field, fieldType) => isZeroValue field fieldType
/-- The zero of the field type `type`, held in the slots of a sum's inactive constructors:
0 for a word or float, and the first constructor of an enumeration. -/
def zeroOf (type : Lean.Expr) : MetaM Lean.Expr := do
if (← isUInt64 type) || (← isFloat type) || (← isFloat32 type) then return ← mkNumeral type 0
if let some (.enum (ctor :: _)) ← userType? type then return mkConst ctor
throwError "a field of a sum must be a word, a float, or an enumeration: {type}"
/-- The parts of `term`, a constructor application of the pair, structure, or sum type
`type`, with their types. The parts of a sum's constructor are its index, then each
constructor's fields: the applied constructor's arguments, and zeros for the others. -/
def constructorParts? (term type : Lean.Expr) : MetaM (Option (List (Lean.Expr × Lean.Expr))) := do
let type ← whnfR type
let term := term.consumeMData
if type.isAppOfArity ``Prod 2 then
let (``Prod.mk, #[first, second, a, b]) := term.getAppFnArgs | return none
return some [(a, first), (b, second)]
match ← userType? type with
| some (.struct ctor fields) =>
unless term.isAppOfArity ctor fields.length do return none
return some (term.getAppArgs.toList.zip fields)
| some (.sum ctors) =>
let some index := ctors.findIdx? fun (ctor, fields) => term.isAppOfArity ctor fields.length
| return none
let word := mkConst ``UInt64
let mut parts := [(← mkNumeral word index, word)]
for h : i in [:ctors.length] do
let fields := ctors[i].2
if i == index then
parts := parts ++ term.getAppArgs.toList.zip fields
else
parts := parts ++ (← fields.mapM fun field => return (← zeroOf field, field))
return some parts
| _ => return none
/-- The component terms of `term`, a nest of constructor applications of type `type`. -/
partial def stateTerms (term type : Lean.Expr) : MetaM (List (Lean.Expr × ScalarType)) := do
if (← isUInt64List type) || (← isNodeType type) then return [(term, .u64)]
if ← isTupleType type then
let some parts ← constructorParts? term type
| throwError "a loop state must be a tuple of components: {← sourceOf term}"
return (← parts.mapM fun (part, partType) => stateTerms part partType).flatten
return [(term, ← scalarTypeOf type)]
instance : Inhabited (Σ type, IRExpr type) := ⟨⟨.u64, .const 0⟩⟩
instance : Inhabited (IRExpr .bool) := ⟨.ltU (.const 0) (.const 0)⟩
/-- The WebAssembly value types of a result's components, array pointers included. -/
def resultTypes (type : Lean.Expr) : MetaM (List ScalarType) := componentTypes type true
/-- `term` with every redex at its head reduced, including those that a reduction exposes. -/
partial def headBetaAll (term : Lean.Expr) : Lean.Expr :=
if term.isHeadBetaTarget then headBetaAll term.headBeta else term
/-- Whether the sparse case split `name`, which Lean generates for a match with a wildcard,
is an auxiliary `_sparseCasesOn` definition. -/
def isSparseCasesOn : Name → Bool
| .str _ s => s.startsWith "_sparseCasesOn"
| _ => false
/-- An alternative of a case split: the field types of its constructor, and the function
that takes the fields and then the further arguments `extra`. -/
structure CaseAlt where
fields : List Lean.Expr
fn : Lean.Expr
extra : Array Lean.Expr
/-- The alternative's body for the field variables `xs`. -/
def CaseAlt.body (alt : CaseAlt) (xs : List Lean.Expr) : Lean.Expr :=
headBetaAll (mkAppN alt.fn (xs.toArray ++ alt.extra))
/-- The discriminant and the alternatives, one per constructor in order, of a case split on
an enumeration or a sum: `T.casesOn`, `T.rec` applied to further arguments, or an auxiliary
`_sparseCasesOn` definition, which unfolds to `T.rec`. -/
partial def userCases? (term : Lean.Expr) : MetaM (Option (Lean.Expr × List CaseAlt)) := do
let term := headBetaAll term.consumeMData
let .const name _ := term.getAppFn | return none
let args := term.getAppArgs
if isSparseCasesOn name then
let some unfolded ← unfoldDefinition? term | return none
return ← userCases? unfolded
let .str induct kind := name | return none
unless kind == "casesOn" || kind == "rec" do return none
let ctorFields ← match ← userType? (.const induct []) with
| some (.enum ctors) => pure (ctors.map fun _ => [])
| some (.sum ctors) => pure (ctors.map (·.2))
-- The alternatives of `rec` on a recursive type also take induction hypotheses.
| some (.recursive ctors) =>
unless kind == "casesOn" do return none
pure (ctors.map (·.2))
| _ => return none
let n := ctorFields.length
if kind == "casesOn" && args.size == 2 + n then
return some (args[1]!, ((args.extract 2 args.size).toList.zip ctorFields).map
fun (alt, fields) => ⟨fields, alt, #[]⟩)
if kind == "rec" && args.size ≥ 2 + n then
let extra := args.extract (2 + n) args.size
return some (args[1 + n]!, ((args.extract 1 (1 + n)).toList.zip ctorFields).map
fun (minor, fields) => ⟨fields, minor, extra⟩)
return none
/-- The field types and the alternative of a `casesOn` on a pair or a structure, with its
discriminant. -/
def tupleCases? (term : Lean.Expr) : MetaM (Option (Lean.Expr × List Lean.Expr × Lean.Expr)) := do
match term.getAppFnArgs with
| (``Prod.casesOn, #[first, second, _, pair, alternative]) =>
return some (pair, [first, second], alternative)
| (.str induct "casesOn", #[_, value, alternative]) =>
let some (.struct _ fields) ← userType? (.const induct []) | return none
return some (value, fields, alternative)
| _ => return none
/-- A fresh local of type `type`. -/
def fresh (type : ScalarType) (name : String) : CompileM Nat := do
let p ← get
set { p with vars := p.vars.push type, names := p.names.push (name, p.next) }
return p.next
/-- Binds `x` to the locals `components`: a variable for one component, a tuple
otherwise. -/
def Ctx.bind (ctx : Ctx) (x : Lean.Expr) : List (Nat × ScalarType) → Ctx
| [(index, .f64)] | [(index, .f32)] => { ctx with floats := (x, index) :: ctx.floats }
| [(index, _)] => { ctx with words := (x, index) :: ctx.words }
| components => { ctx with tuples := (x, components) :: ctx.tuples }
/-- The array variables in scope and their locals: word, float, and record arrays. -/
def Ctx.arrayVars (ctx : Ctx) : List (Lean.Expr × Nat) :=
ctx.arrays ++ ctx.floatArrays ++ ctx.recordArrays.map fun (x, local_, _) => (x, local_)
/-- Binds `x`, of type `type`, to the locals `components`: an array to its one local, and
any other value as `Ctx.bind` does. -/
def Ctx.bindTyped (ctx : Ctx) (x type : Lean.Expr) (components : List (Nat × ScalarType)) :
MetaM Ctx := do
if ← isUInt64Array type then
let [(index, _)] := components | throwError "an array takes one local"
return { ctx with arrays := (x, index) :: ctx.arrays }
if ← isFloatArray type then
let [(index, _)] := components | throwError "an array takes one local"
return { ctx with floatArrays := (x, index) :: ctx.floatArrays }
if let some elementComponents ← recordArrayComponents? type then
let [(index, _)] := components | throwError "an array takes one local"
return { ctx with recordArrays := (x, index, elementComponents) :: ctx.recordArrays }
if (← isNodeType type) || (← isUInt64List type) then
let [(index, _)] := components | throwError "a value with records takes one local"
return { ctx with nodes := (x, index) :: ctx.nodes }
return ctx.bind x components
/-- Whether `type` is a nest of at least two word arrays, `Array UInt64 × (… × Array UInt64)`. -/
partial def isArrayNest (type : Lean.Expr) : MetaM Bool := do
let type ← whnfR type
unless type.isAppOfArity ``Prod 2 do return false
unless ← isUInt64Array type.appFn!.appArg! do return false
if ← isUInt64Array type.appArg! then return true
isArrayNest type.appArg!
/-- The components of `term`, a nest of `Prod.mk` of the nest type `type`, or `none` when
`term` is not such a nest. -/
partial def nestTerms? (term type : Lean.Expr) : MetaM (Option (List Lean.Expr)) := do
let type ← whnfR type
unless type.isAppOfArity ``Prod 2 do return some [term.consumeMData]
let (``Prod.mk, #[_, _, a, b]) := term.consumeMData.getAppFnArgs | return none
return (a.consumeMData :: ·) <$> (← nestTerms? b type.appArg!)
/-- The unfolding of a `match` auxiliary definition applied to its arguments. -/
def unfoldMatcher? (term : Lean.Expr) : MetaM (Option Lean.Expr) := do
let .const name levels := term.getAppFn | return none
unless ← isMatcher name do return none
let value ← instantiateValueLevelParams (← getConstInfo name) levels
return value.beta term.getAppArgs
/-- Where a loop's body code starts when its code starts at `loc`: inside the
block and loop of the `while`, after the count, the two assignments, the
condition, and the exit test. -/
def loopBodyLoc (loc : Loc) (count : IRExpr .u64) : Loc :=
(((loc.skip (exprLength count + 3)).inside none).inside none).skip
(exprLength (.ltU (.get 0) (.get 0) : IRExpr .bool) + 2)
/-- Where the per-element statement of the copying template starts when the
template's code starts at `loc`: inside the block and loop of its `while`, after
the condition and the exit test. -/
def buildBodyLoc (loc : Loc) (dst limit index : Nat) (count : IRExpr .u64) : Loc :=
let before : List LeanExe.IR.Stmt := [.assign limit count,
.ite (.ltU (.get limit) (.const 536870912)) .skip .abort,
.call 0 [⟨.u64, .bin .mul (.bin .add (.get limit) (.const 1)) (.const 8)⟩] [dst],
.store (.get dst) (.get limit), .assign index (.const 0)]
(((loc.skip (before.map stmtLength).sum).inside none).inside none).skip
(exprLength (.ltU (.get index) (.get limit) : IRExpr .bool) + 2)
/-- Where the per-element statement of the record template starts. -/
def recordBuildBodyLoc (loc : Loc) (dst limit index : Nat) (count : IRExpr .u64)
(elements : List (IRExpr .u64)) : Loc :=
let k : IRExpr .u64 := .const (UInt64.ofNat elements.length)
let before : List LeanExe.IR.Stmt := [.assign limit count,
.ite (.ltU (.get limit) (.const (UInt64.ofNat (536870911 / elements.length + 1)))) .skip .abort,
.call 0 [⟨.u64, .bin .mul (.bin .add (.bin .mul (.get limit) k) (.const 1)) (.const 8)⟩] [dst],
.store (.get dst) (.bin .mul (.get limit) k), .assign index (.const 0)]
(((loc.skip (before.map stmtLength).sum).inside none).inside none).skip
(exprLength (.ltU (.get index) (.get limit) : IRExpr .bool) + 2)
/-- Where the element code of the copying template starts: after the per-element
statement, of `bodyLength` instructions, the element address, and its wrap to 32
bits. -/
def buildElementLoc (loc : Loc) (dst limit index : Nat) (count : IRExpr .u64)
(bodyLength : Nat := 0) : Loc :=
let address : IRExpr .u64 :=
.bin .add (.get dst) (.bin .mul (.bin .add (.get index) (.const 1)) (.const 8))
(buildBodyLoc loc dst limit index count).skip (bodyLength + exprLength address + 1)
/-- Where the value of slot `k` starts in `Stmt.record dst values mask` when the record's code
starts at `loc`: after the allocation, the header stores, the earlier slots' stores, and the
slot's address. -/
def recordValueLoc (loc : Loc) (dst : Nat) (values : List (IRExpr .u64)) (mask : UInt64)
(k : Nat) : Loc :=
loc.skip (stmtLength (Stmt.record dst (values.take k) mask) +
exprLength (.bin .add (.get dst) (.const 0) : IRExpr .u64) + 1)
/-- The hint for code at `path` inside the code a hint's path is relative to. -/
def Hint.within (path : List Nat) (hint : Hint) : Hint := { hint with path := path ++ hint.path }
/-- Runs `action` with an empty statement list and returns its statements and
hints, relative to their own start, while keeping the locals it allocates. -/
def withBlock (action : CompileM α) : CompileM (α × List LeanExe.IR.Stmt × List Hint) := do
let saved ← get
set { saved with stmts := #[], hints := #[], length := 0 }
let result ← action
let inner ← get
set { inner with stmts := saved.stmts, hints := saved.hints, length := saved.length }
return (result, inner.stmts.toList, inner.hints.toList)
/-- Pushes `stmt` to the prelude with its hints, which are relative to its start. -/
def pushStmt (stmt : LeanExe.IR.Stmt) (hints : List Hint) : CompileM Unit :=
modify fun p => { p with
stmts := p.stmts.push stmt
hints := p.hints ++ (hints.map (Hint.shift p.length)).toArray
length := p.length + stmtLength stmt }
/-- The local of the array variable `term` in `vars`, which the code must not have moved. -/
def lookupArray (vars : List (Lean.Expr × Nat)) (term : Lean.Expr) : CompileM (Option Nat) := do
let some local_ := vars.lookup term | return none
if (← get).consumed.contains term then
throwError "the array {← sourceOf term} is used after the code moved it"
return some local_
/-- The local of the variable `term` of a recursive type, which the code must not have
moved. -/
def lookupNode (ctx : Ctx) (term : Lean.Expr) : CompileM (Option Nat) := do
let some local_ := ctx.nodes.lookup term | return none
if (← get).consumed.contains term then
throwError "the value {← sourceOf term} is used after the code moved it"
return some local_
/-- Records that the code has moved the owned parameter `param`. -/
def markMoved (param : Lean.Expr) : CompileM Unit := do
if (← get).pendingReads.contains param then
throwError "the value {← sourceOf param} moves before an earlier part of the same value reads it"
modify fun p => { p with consumed := param :: p.consumed }
/-- The locals that `e` reads: those it gets, including the pointers of arrays and values of
recursive types that it passes on, and the arrays whose elements it reads. -/
def readLocals : {type : ScalarType} → IRExpr type → List Nat
| _, .get index => [index]
| _, .read array position => array :: readLocals position
| _, .bin _ l r => readLocals l ++ readLocals r
| _, .eq l r => readLocals l ++ readLocals r
| _, .ne l r => readLocals l ++ readLocals r
| _, .ltU l r => readLocals l ++ readLocals r
| _, .leU l r => readLocals l ++ readLocals r
| _, .and l r => readLocals l ++ readLocals r
| _, .or l r => readLocals l ++ readLocals r
| _, .binF _ l r => readLocals l ++ readLocals r
| _, .binF32 _ l r => readLocals l ++ readLocals r
| _, .eqF l r => readLocals l ++ readLocals r
| _, .ltF l r => readLocals l ++ readLocals r
| _, .leF l r => readLocals l ++ readLocals r
| _, .not c => readLocals c
| _, .unF _ x => readLocals x
| _, .unF32 _ x => readLocals x
| _, .ofBits32 x => readLocals x
| _, .toBits32 x => readLocals x
| _, .eqF32 l r => readLocals l ++ readLocals r
| _, .ltF32 l r => readLocals l ++ readLocals r
| _, .leF32 l r => readLocals l ++ readLocals r
| _, .iteF32 c a b => readLocals c ++ readLocals a ++ readLocals b
| _, .convertU x => readLocals x
| _, .truncSatU x => readLocals x
| _, .ofBits x => readLocals x
| _, .toBits x => readLocals x
| _, .ite c a b => readLocals c ++ readLocals a ++ readLocals b
| _, .iteF c a b => readLocals c ++ readLocals a ++ readLocals b
| _, .const _ | _, .bconst _ | _, .getF _ | _, .constF _ | _, .getF32 _ | _, .constF32 _ => []
/-- Runs `action`, which translates a later part of a value, while the arrays and values of
recursive types at the locals `reads`, which earlier parts read, count as pending reads: the
earlier parts run after `action`'s statements, so `action` may not move them. -/
def afterReads {γ : Type} (ctx : Ctx) (reads : List Nat) (action : CompileM γ) : CompileM γ := do
let arrays := (ctx.arrayVars ++ ctx.nodes).filterMap fun (x, local_) =>
if reads.contains local_ then some x else none
let saved := (← get).pendingReads
modify fun p => { p with pendingReads := arrays ++ p.pendingReads }
let result ← action
modify fun p => { p with pendingReads := saved }
return result
/-- The number of occurrences of the free variable `x` in `term`. -/
partial def occurrences (x : FVarId) : Lean.Expr → Nat
| .fvar y => if y == x then 1 else 0
| .app f a => occurrences x f + occurrences x a
| .lam _ t b _ | .forallE _ t b _ => occurrences x t + occurrences x b
| .letE _ t v b _ => occurrences x t + occurrences x v + occurrences x b
| .mdata _ e | .proj _ _ e => occurrences x e
| _ => 0
/-- `ctx` for the value of a `let` whose body is `body`: the value may move only the owned values
that the body does not use. -/
def Ctx.movableIn (ctx : Ctx) (body : Lean.Expr) : Ctx :=
{ ctx with owned := ctx.owned.filter fun x => occurrences x.fvarId! body == 0 }
/-- `ctx` for a part of a value that the parts `later` follow: the part may move only the owned
arrays and values of recursive types that no later part mentions, since the later parts use
them. -/
def Ctx.before (ctx : Ctx) (later : List Lean.Expr) : Ctx :=
let arrays := (ctx.arrayVars ++ ctx.nodes).map (·.1)
{ ctx with owned := ctx.owned.filter fun x =>
!arrays.contains x || later.all fun t => occurrences x.fvarId! t == 0 }
/-- Runs `k` with a fresh variable of each type in `types`. -/
def withVars {γ : Type} : List Lean.Expr → (List Lean.Expr → MetaM γ) → MetaM γ
| [], k => k []
| type :: types, k => withLocalDeclD `field type fun x => withVars types fun xs => k (x :: xs)
/-- For each result component of `type`, whether it points to heap data the value owns: an
array, a list, or a value of a recursive type. -/
partial def heapComponents (type : Lean.Expr) : MetaM (List Bool) := do
let type ← whnfR type
if type.isAppOfArity ``Prod 2 then
return (← heapComponents type.appFn!.appArg!) ++ (← heapComponents type.appArg!)
if (← isArray type) || (← isUInt64List type) || (← isNodeType type) then return [true]
return (← resultTypes type).map fun _ => false
/-- The array parameters and parameters of recursive types among `params` that the result
term `term` moves on some path, through its `let`s, matches, branches, and pairs: those it
returns, passes as the left operand of `++`, passes at an owned position of a callee in
`owners`, places in the initial state of a loop, or places in a constructor of a recursive
type. A match on a value of a recursive
type moves the value when its record branch moves one of the record's children. -/
partial def moveSites (owners : List (Name × List Nat)) (params : List Lean.Expr)
(term : Lean.Expr) : MetaM (List Lean.Expr) := do
let term := term.consumeMData.headBeta
if let .letE name type value body _ := term then
-- A `let` of an array or tree variable is the variable, and a value moves only what the
-- body does not use.
if value.consumeMData.isFVar && ((← isArray type) || (← isNodeType type)) then
return ← moveSites owners params (body.instantiate1 value.consumeMData)
let inValue := (← moveSites owners params value).filter fun x =>
occurrences x.fvarId! body == 0
return inValue ++ (← withLocalDeclD name type fun x =>
moveSites owners params (body.instantiate1 x))
if params.contains term then return [term]
if let .proj _ _ pair := term then return ← moveSites owners params pair
if let some unfolded ← unfoldMatcher? term then return ← moveSites owners params unfolded
if let some (discriminant, alternatives) ← userCases? term then
if ← isNodeType (← inferType discriminant) then
let discriminant := discriminant.consumeMData
let mut sites := []
for alternative in alternatives do
sites := sites ++ (← withVars alternative.fields fun xs => do
let children ← xs.filterM fun x => do isNodeType (← inferType x)
let inner ← moveSites owners (params ++ children) (alternative.body xs)
let outer := inner.filter (!children.contains ·)
return if params.contains discriminant && inner.any children.contains
then discriminant :: outer else outer)
return sites
if let some (fields, _) ← recordCell? term then
return (← fields.mapM (moveSites owners params)).flatten
match term.getAppFnArgs with
| (``Prod.casesOn, #[_, _, _, pair, alternative]) =>
return (← moveSites owners params pair) ++
(← lambdaTelescope alternative fun _ body => moveSites owners params body)
| (``Prod.fst, #[_, _, pair]) | (``Prod.snd, #[_, _, pair]) =>
moveSites owners params pair
| (``ite, #[type, _, _, a, b]) =>
if (← whnfR type).isAppOfArity ``Prod 2 || (← isArray type) || (← isNodeType type) then
return (← moveSites owners params a) ++ (← moveSites owners params b)
return []
| (``Prod.mk, #[_, _, a, b]) =>
-- The first part moves only the arrays and values of recursive types that the second does
-- not use; it copies the rest.
let first ← (← moveSites owners params a).filterM fun x => do
let type ← inferType x
return !((← isArray type) || (← isNodeType type)) || occurrences x.fvarId! b == 0
return first ++ (← moveSites owners params b)
| (``LeanExe.loop, #[stateType, _, init, _]) =>
-- A loop over a nest of arrays moves its initial components.
unless ← isArrayNest stateType do return []
let some terms ← nestTerms? init stateType | return []
return terms.filter params.contains
| (``LeanExe.repeatWhile, #[_, _, init, _, _]) =>
-- A `repeatWhile` moves the arrays of its initial state.
moveSites owners params init
| (``HAppend.hAppend, #[_, _, _, _, left, _]) =>
return if params.contains left.consumeMData then [left.consumeMData] else []
| (``Array.set!, #[_, array, _, _]) | (``Array.setIfInBounds, #[_, array, _, _])
| (``Array.insertIdx!, #[_, array, _, _]) | (``Array.eraseIdxIfInBounds, #[_, array, _])
| (``Array.push, #[_, array, _]) =>
moveSites owners params array
| (fn, args) =>
return (← ((owners.lookup fn).getD []).mapM fun i => do
let some arg := args[i]? | return []
-- A value that another array or tree argument names is copied at the owned position,
-- so the call does not move it.
let others ← (args.toList.eraseIdx i).filterM fun a => do
let type ← inferType a
return (← isArray type) || (← isNodeType type)
return (← moveSites owners params arg).filter fun x =>
others.all fun a => occurrences x.fvarId! a == 0).flatten
/-- The arguments at position `i` of the calls of `self` in `term`. -/
partial def selfArgs (self : Name) (i : Nat) (term : Lean.Expr) : MetaM (List Lean.Expr) := do
let term := term.consumeMData.headBeta
if let .letE n t v body _ := term then
return (← selfArgs self i v) ++
(← withLocalDeclD n t fun x => selfArgs self i (body.instantiate1 x))
if let some unfolded ← unfoldMatcher? term then return ← selfArgs self i unfolded
if let some (discriminant, alternatives) ← userCases? term then
let mut out ← selfArgs self i discriminant
for alternative in alternatives do
out := out ++ (← withVars alternative.fields fun xs => selfArgs self i (alternative.body xs))
return out
match term with
| .app .. =>
let args := term.getAppArgs
let here := if term.getAppFn.isConstOf self then (args[i]?.map (·.consumeMData)).toList
else []
return here ++ (← args.toList.mapM (selfArgs self i)).flatten
| .lam .. => lambdaTelescope term fun _ body => selfArgs self i body
| _ => return []
/-- Releases the owned parameters that the code has not moved on the current path. -/
def releaseUnmoved (ctx : Ctx) : CompileM Unit := do
for param in ctx.owned do
unless (← get).consumed.contains param do
let some local_ := (ctx.nodes ++ ctx.arrayVars).lookup param
| throwError "an owned parameter has no local"
let stmt := LeanExe.IR.Stmt.release local_
pushStmt stmt [mkHint ⟨[], 0⟩ (stmtLength stmt) "release owned parameter" (← sourceOf param)]
markMoved param
/-- Makes the arrays, lists, and values of recursive types among `xs`, the components of a
call's result or of an owned pair, owned temporaries, which the code may move and the function
releases otherwise, and marks the pairs among them as owned pairs. -/
def ownComponents (ctx : Ctx) (source : String) (xs : List (Lean.Expr × Lean.Expr)) :
CompileM Ctx := do
let mut ctx := ctx
for (x, type) in xs do
if (← isArray type) || (← isUInt64List type) || (← isNodeType type) then
unless ctx.temporaries && ctx.foldable && ctx.allocating do
throwError "an array, a list, or a value of a recursive type may be bound from a call's result only at the top of a function body: {source}"
let some local_ := (ctx.arrayVars ++ ctx.nodes).lookup x
| throwError "a heap component has no local: {source}"
modify fun p => { p with temporaries := p.temporaries.push (x, local_, source) }
ctx := { ctx with owned := x :: ctx.owned }
else if ← isTupleType type then
ctx := { ctx with owned := x :: ctx.owned }
return ctx
/-- Binds a fresh variable for each field type in `fields` to its slice of `components`, of
the lengths `widths`, in order, and continues with the variables. -/
def bindFields {γ : Type} (ctx : Ctx) (k : Ctx → List Lean.Expr → CompileM γ) :
List Lean.Expr → List Nat → List (Nat × ScalarType) → List Lean.Expr → CompileM γ
| field :: fields, width :: widths, components, xs =>
withLocalDeclD `field field fun x => do
let ctx ← ctx.bindTyped x field (components.take width)
bindFields ctx k fields widths (components.drop width) (xs ++ [x])
| _, _, _, xs => k ctx xs
/-- The field types of the pair or structure type `type`. -/
def tupleFields? (type : Lean.Expr) : MetaM (Option (List Lean.Expr)) := do
let type ← whnfR type
if type.isAppOfArity ``Prod 2 then return some [type.appFn!.appArg!, type.appArg!]
let some (.struct _ fields) ← userType? type | return none
return some fields
/-- Runs `k` on the body of `alternative` with its fields bound to `slots`. -/
def caseBody {γ : Type} (ctx : Ctx) (slots : List (Nat × ScalarType)) (alternative : CaseAlt)
(k : Ctx → Lean.Expr → CompileM γ) : CompileM γ := do
let widths ← alternative.fields.mapM fun field => return (← stateTypes field).length
bindFields ctx (fun ctx xs => k ctx (alternative.body xs)) alternative.fields widths slots []
/-- Binds the fields `fields`, from slot `i` on, of the record at local `ptr` to fresh locals,
and continues with `k` given the field variables and the loads that fill the locals. A word
or float field is loaded as its value, and a field of a recursive type as its pointer. -/
partial def bindRecordFields {γ : Type} (ctx : Ctx) (ptr : Nat)
(k : Ctx → List Lean.Expr → List LeanExe.IR.Stmt → CompileM γ) :
Nat → List Lean.Expr → List Lean.Expr → List LeanExe.IR.Stmt → CompileM γ
| _, [], xs, loads => k ctx xs loads
| i, field :: fields, xs, loads => do
let address : IRExpr .u64 := .bin .add (.get ptr) (.const (UInt64.ofNat (8 * i)))
withLocalDeclD `field field fun x => do
if ← isFloat field then
let local_ ← fresh .f64 "field"
bindRecordFields { ctx with floats := (x, local_) :: ctx.floats } ptr k (i + 1) fields
(xs ++ [x]) (loads ++ [.load .f64 local_ address])
else if ← isWordType field then
let local_ ← fresh .u64 "field"
bindRecordFields { ctx with words := (x, local_) :: ctx.words } ptr k (i + 1) fields
(xs ++ [x]) (loads ++ [.load .u64 local_ address])
else if ← isNodeType field then
let local_ ← fresh .u64 "field"
bindRecordFields { ctx with nodes := (x, local_) :: ctx.nodes } ptr k (i + 1) fields
(xs ++ [x]) (loads ++ [.load .u64 local_ address])
else
throwError "a field of a recursive type must be a word, a float, or a value of a recursive type: {field}"
/-- Hints for the statements `stmts`, which follow one another from `loc`, each with its rule. -/