Repository navigation
Expand file tree
/
Copy pathCompile.lean
More file actions
1250 lines (1148 loc) · 70 KB
/
Copy pathCompile.lean
File metadata and controls
1250 lines (1148 loc) · 70 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 Verified.Source
import Verified.Literals
import Verified.ListFoldDefs
import Verified.LocalFrame
import Verified.TreeCopyCode
import Verified.TreeHeaderCode
import Verified.TreeFieldsCode
import Verified.Bytes.Code
import Verified.Bytes.BuildCode
import Verified.Bytes.LiteralCode
import LeanExe.Runtime.Defs
/-! The verified compiler: a Lean function from source functions to a WebAssembly module. The
module has the layout of `LeanExe`'s modules, with the runtime's `alloc` and `release` at
functions 0 and 1, so the runtime and its proofs serve both compilers. -/
namespace Verified
open Wasm
/-- The WebAssembly types of the words that hold an element, in order. -/
def Elem.types : Elem → List ValueType
| .word | .bool => [.i64]
| .float => [.f64]
| .float32 => [.f32]
| .prod a b => a.types ++ b.types
@[simp] theorem Elem.types_word : Elem.types .word = [.i64] := rfl
@[simp] theorem Elem.types_bool : Elem.types .bool = [.i64] := rfl
@[simp] theorem Elem.types_float : Elem.types .float = [.f64] := rfl
@[simp] theorem Elem.types_float32 : Elem.types .float32 = [.f32] := rfl
/-- The WebAssembly types of the words that hold a value of type `t`, in order. -/
def Ty.types : Ty → List ValueType
| .elem e => e.types
| .array _ | .tree _ | .treeHeader _ _ => [.i64]
| .bytes => [.i64, .i64]
| .pair a b => a.types ++ b.types
/-- The word count `k` of an element as the code uses it, at most `2 ^ 29`. An element of
`2 ^ 29` words or more is in no array that memory holds, so the code may use any positive count
for it, and `2 ^ 29` keeps the count positive and below `2 ^ 64`. -/
def wordCount (k : Nat) : UInt64 := UInt64.ofNat (min k 536870912)
/-- The instructions that multiply the word on top of the stack by the word count `k`, none for
1. -/
def scaleCode (k : Nat) : Program :=
if k = 1 then [] else [.constI64 (wordCount k), .mulI64]
/-- The instructions that divide the word on top of the stack by the word count `k`, none for
1. -/
def divCode (k : Nat) : Program :=
if k = 1 then [] else [.constI64 (wordCount k), .divUI64]
/-- The scratch locals that an operation needs for itself: two for division and remainder, which
save their operands to test the divisor. -/
def BinOp.scratch : BinOp → Nat
| .div | .rem => 2
| _ => 0
/-- The instructions of an operation after the code of its operands, with the first scratch
local `base`. WebAssembly traps on a zero divisor, so division and remainder save their operands
in locals `base` and `base + 1`, test the divisor, and give Lean's result for zero: 0 for division
and the dividend for the remainder. -/
def BinOp.code (op : BinOp) (base : Nat) : Program :=
match op with
| .add => [.addI64]
| .sub => [.subI64]
| .mul => [.mulI64]
| .div => [.localSet (base + 1), .localSet base, .localGet (base + 1), .eqzI64,
.iff 0 1 [.constI64 0] [.localGet base, .localGet (base + 1), .divUI64] [] [.i64]]
| .rem => [.localSet (base + 1), .localSet base, .localGet (base + 1), .eqzI64,
.iff 0 1 [.localGet base] [.localGet base, .localGet (base + 1), .remUI64] [] [.i64]]
| .and => [.andI64]
| .or => [.orI64]
| .xor => [.xorI64]
| .shl => [.shlI64]
| .shr => [.shrUI64]
/-- The WebAssembly comparison of a comparison. -/
def CmpOp.instr : CmpOp → Instruction
| .eq => .eqI64
| .ne => .neI64
| .lt => .ltUI64
| .le => .leUI64
def FBinOp.instr : FBinOp → Instruction
| .add => .f64Add
| .sub => .f64Sub
| .mul => .f64Mul
| .div => .f64Div
def FCmpOp.instr : FCmpOp → Instruction
| .lt => .f64Lt
| .le => .f64Le
| .eq => .f64Eq
def FBinOp.instr32 : FBinOp → Instruction
| .add => .f32Add
| .sub => .f32Sub
| .mul => .f32Mul
| .div => .f32Div
def FCmpOp.instr32 : FCmpOp → Instruction
| .lt => .f32Lt
| .le => .f32Le
| .eq => .f32Eq
def F32UnOp.code (op : F32UnOp) (operand : Program) : Program :=
match op with
| .sqrt => operand ++ [.f32Sqrt]
| .abs => operand ++ [.f32Abs]
| .neg => .f32Const 0x80000000 :: operand ++ [.f32Sub]
| .nearest => operand ++ [.f32Nearest]
def ToFloat32.code : ToFloat32 → Program
| .convert => [.f32ConvertI64U]
| .ofBits => [.wrapI64, .f32ReinterpretI32, .f32Const 0x80000000, .f32Add]
| .signed32 => [.wrapI64, .f32ConvertI32S]
def ToWord32.code : ToWord32 → Program
| .truncate => [.i64TruncSatF32U]
| .toBits => [.i32ReinterpretF32, .extendUI32]
| .signed32 => [.i32TruncSatF32S, .extendUI32]
/-- The instructions of an operation on one float after the code of its operand. The negation
subtracts from negative zero, since Talos's `f64.neg` keeps a NaN's payload and flips its sign
while Lean has one NaN. -/
def FUnOp.code (op : FUnOp) (operand : Program) : Program :=
match op with
| .sqrt => operand ++ [.f64Sqrt]
| .abs => operand ++ [.f64Abs]
| .neg => .f64Const 0x8000000000000000 :: operand ++ [.f64Sub]
/-- The instructions of a conversion of a word to a float after the code of its operand.
`Float.ofBits` reinterprets the word and adds negative zero, which gives the canonical NaN for a
NaN and leaves every other float unchanged. -/
def ToFloat.code : ToFloat → Program
| .convert => [.f64ConvertI64U]
| .ofBits => [.f64ReinterpretI64, .f64Const 0x8000000000000000, .f64Add]
def ToWord.instr : ToWord → Instruction
| .truncate => .i64TruncSatF64U
| .toBits => .i64ReinterpretF64
/-- The largest of the numbers `w i`, or 0. -/
def argsMax : {n : Nat} → ((i : Fin n) → Nat) → Nat
| 0, _ => 0
| _ + 1, w => max (w ⟨0, by omega⟩) (argsMax fun i => w i.succ)
/-- Code that runs the program paired with the tag that `tag` pushes: tests of the tag against
each pair's first word in order, the last pair untested. -/
def dispatchChain (tag : Program) : List (UInt64 × Program) → Program
| [] => []
| [(_, code)] => code
| (i, code) :: p :: rest =>
tag ++ [.constI64 i, .eqI64, .iff 0 0 code (dispatchChain tag (p :: rest)) [] []]
/-- The instructions of the arguments, in order. -/
def argsCode : {n : Nat} → ((i : Fin n) → Program) → Program
| 0, _ => []
| _ + 1, c => c ⟨0, by omega⟩ ++ argsCode fun i => c i.succ
/-- The module index of a function that an expression calls: the functions `S` occupy the module
positions from 2, the last of `S` first. -/
def FVar.callIndex {S : List Sig} {g : Sig} (f : FVar S g) : Nat :=
2 + (S.length - 1 - f.index)
/-- The instructions that push the words of types `tys` at positions `loc` on, in order. -/
def loadCode (h loc : Nat) : List ValueType → Program
| [] => []
| ty :: tys => .localGet (slotIndex h loc ty) :: loadCode h (loc + 1) tys
/-- The instructions that store the top words of the stack, of types `tys`, at positions `loc`
on, the top word at the last of them. -/
def storeCode (h loc : Nat) : List ValueType → Program
| [] => []
| ty :: tys => storeCode h (loc + 1) tys ++ [.localSet (slotIndex h loc ty)]
/-- Where a variable's words start, and its mode. -/
structure Slot where
loc : Nat
mode : Mode
deriving Inhabited
/-- The variables live in a body that binds `k` new variables, given those live after the
binder: the new variables are dead after the body. -/
def shift (k : Nat) (live : Nat → Bool) (i : Nat) : Bool := if i < k then false else live (i - k)
/-- The number of words that hold the values of the types. -/
def widthSum (ts : List Ty) : Nat := (ts.map Ty.width).sum
/-- The loop that moves the words of the array at the address in local `ptr` from the position in
local `lo` up to the position in local `index` up by `k` words, from the top down, so that it reads
each word before it overwrites it. Local `index` counts down to the value of `lo`. -/
def shiftUpCode (ptr lo index k : Nat) : Program :=
[.block 0 0 [.loop 0 0 [.localGet index, .localGet lo, .leUI64, .br_if 1,
.localGet index, .constI64 1, .subI64, .localSet index,
.localGet ptr, .localGet index, .constI64 (UInt64.ofNat (k + 1)), .addI64, .constI64 8,
.mulI64, .addI64, .wrapI64,
.localGet ptr, .localGet index, .constI64 1, .addI64, .constI64 8, .mulI64, .addI64,
.wrapI64, .load64 0, .store64 0, .br 0]]]
/-- The instructions that push the address of word `w0 + j` of the array at the address in local
`ptr`, with `w0` in local `w0`. -/
def wordAddrCode (ptr w0 j : Nat) : Program :=
[.localGet ptr, .localGet w0, .constI64 (UInt64.ofNat (j + 1)), .addI64, .constI64 8, .mulI64,
.addI64, .wrapI64]
/-- The instructions that push the words `w0 + j` on, of types `tys`, of the array at the address
in local `ptr`, each when the flag in local `flag` is 1, and 0 in its place otherwise. -/
def loadWordsCode (ptr w0 flag : Nat) : Nat → List ValueType → Program
| _, [] => []
| j, ty :: tys =>
[.localGet flag, .wrapI64, .iff 0 1 (wordAddrCode ptr w0 j ++ [.load64 0]) [.constI64 0] []
[.i64]] ++ ofWordCode ty ++ loadWordsCode ptr w0 flag (j + 1) tys
/-- The instructions that write the words of types `tys` at positions `src` on as the words
`w0 + j` on of the array at the address in local `ptr`. -/
def storeWordsCode (h ptr w0 : Nat) : Nat → Nat → List ValueType → Program
| _, _, [] => []
| j, src, ty :: tys =>
wordAddrCode ptr w0 j ++ .localGet (slotIndex h src ty) :: toWordCode ty ++ [.store64 0] ++
storeWordsCode h ptr w0 (j + 1) (src + 1) tys
/-- The instructions that push a copy of the value of type `t` whose words positions `src` on
hold: each array copied into a new array, and the other words as they are. -/
def copyCode (h : Nat) : Ty → Nat → Nat → Program
| .elem e, src, _ => loadCode h src e.types
| .pair a b, src, base => copyCode h a src base ++ copyCode h b (src + a.width) base
| .array _, src, base => copyArrayCode src base
| .bytes, src, base => Bytes.copyOwnedCode src (src + 1) base
| .tree shape, src, base => treeCopyCode shape src base
| .treeHeader shape r, _, base => treeHeaderCopyCode shape r base
/-- The instructions that release the allocations of the owned value of type `t` whose words locals
`src` on hold. -/
def releaseCode : Ty → Nat → Program
| .elem _, _ => []
| .pair a b, src => releaseCode a src ++ releaseCode b (src + a.width)
| .array _, src | .tree _, src | .treeHeader _ _, src | .bytes, src => [.localGet src, .call 1]
/-- The scratch positions needed to copy a value of type `t`. -/
def Ty.copyScratch : Ty → Nat
| .elem _ => 0
| .array _ => 3
| .bytes => 2
| .tree _ => treeCopyScratch
| .treeHeader _ _ => 1
| .pair a b => max a.copyScratch b.copyScratch
/-- The locals that turning a borrowed value of type `t` on the stack into an owned copy needs:
its words, and three for the copy of each array. -/
def copyWidth (t : Ty) : Nat := if t.scalar then 0 else t.width + t.copyScratch
/-- The instructions that turn the value of type `t` on top of the stack, held in mode `source`,
into one held in mode `target`: a borrowed value that must be owned is stored in the locals from
`base` on and copied. -/
def coerceCode (h : Nat) (t : Ty) (source target : Mode) (base : Nat) : Program :=
if source = .borrowed ∧ target = .owned ∧ t.scalar = false then
storeCode h base t.types ++ copyCode h t base (base + t.width)
else []
/-- The instructions that release variable `i` among the variables of types `Γ` in the slots
`slots`, when it is owned. -/
def releaseVar (Γ : List Ty) (slots : List Slot) (i : Nat) : Program :=
match Γ[i]? with
| some t =>
if (slots.getD i default).mode = .owned then releaseCode t (slots.getD i default).loc else []
| none => []
/-- The instructions that release the owned variables that `sel` selects, by index, among the
variables of types `Γ` in the slots `slots`. -/
def releaseWhere (Γ : List Ty) (slots : List Slot) (sel : Nat → Bool) : Program :=
((List.range Γ.length).filter sel).flatMap (releaseVar Γ slots)
/-- The instructions that push the words of variable `x`: a copy when it is owned and stays live
in `live`, and its own words otherwise, which move it when it is owned. -/
def Var.code (h : Nat) (slots : List Slot) (base : Nat) (live : Nat → Bool) {Γ : List Ty}
{t : Ty} (x : Var Γ t) : Program :=
if (slots.getD x.index default).mode = .owned ∧ live x.index = true then
copyCode h t (slots.getD x.index default).loc base
else loadCode h (slots.getD x.index default).loc t.types
/-- The instructions that push the words of variable `x` as an owned value: its own words when it
is owned and dies, and a copy otherwise. -/
def Var.ownedCode (h : Nat) (slots : List Slot) (base : Nat) (live : Nat → Bool) {Γ : List Ty}
{t : Ty} (x : Var Γ t) : Program :=
x.code h slots base live ++ coerceCode h t (slots.getD x.index default).mode .owned base
/-- The instructions that push the byte count of a block for the length in local `total`: the bytes
that the length needs, or twice the capacity in local `cap` when that is more, and at most
`2 ^ 32`. -/
def requestCode (total cap : Nat) : Program :=
[.localGet total, .constI64 1, .addI64, .constI64 8, .mulI64,
.localGet cap, .constI64 2, .mulI64, .leUI64,
.iff 0 1 [.localGet cap, .constI64 2, .mulI64, .constI64 4294967296, .leUI64,
.iff 0 1 [.localGet cap, .constI64 2, .mulI64] [.constI64 4294967296] [] [.i64]]
[.localGet total, .constI64 1, .addI64, .constI64 8, .mulI64] [] [.i64]]
/-- The instructions that leave in local `b + 4` the address of an owned array whose first
elements are those of `x`, whose length is `x`'s length plus the word in local `ext`, and whose
block has room for that length. Local `b` holds `x`'s address, `b + 1` its length, `b + 2` the
new length, `b + 3` the capacity, and `b + 5` the index of the copy. The code traps at
`unreachable` when the new length is `2 ^ 29` or more. An owned `x` that dies is extended in its
own block when the block has room, and otherwise copied into a block of at least twice its
capacity, which releases the old block; any other `x` is copied. -/
def Var.roomCode (slots : List Slot) (b : Nat) (live : Nat → Bool) {Γ : List Ty}
{e : Elem} (x : Var Γ (.array e)) (ext : Nat) : Program :=
[.localGet (slots.getD x.index default).loc, .localSet b,
.localGet b, .wrapI64, .load64 0, .localSet (b + 1),
.localGet (b + 1), .localGet ext, .addI64, .localSet (b + 2),
.localGet (b + 2), .constI64 536870912, .geUI64, .iff 0 0 [.unreachable] [] [] []] ++
if (slots.getD x.index default).mode = .owned ∧ live x.index = false then
[.localGet b, .constI64 32, .subI64, .wrapI64, .load64 0, .localSet (b + 3),
.localGet (b + 2), .constI64 1, .addI64, .constI64 8, .mulI64, .localGet (b + 3), .leUI64,
.iff 0 0 [.localGet b, .localSet (b + 4), .localGet (b + 4), .wrapI64, .localGet (b + 2),
.store64 0]
(requestCode (b + 2) (b + 3) ++ allocBlockCode (b + 2) (b + 4) ++
copyIntoCode b (b + 4) (b + 1) (b + 5) ++ [.localGet b, .call 1]) [] []]
else
[.localGet (b + 2), .constI64 1, .addI64, .constI64 8, .mulI64] ++
allocBlockCode (b + 2) (b + 4) ++ copyIntoCode b (b + 4) (b + 1) (b + 5)
/-- The instructions that push the words of an argument at an owned parameter, a variable, as an
owned value. -/
def Expr.ownedCode (h : Nat) (slots : List Slot) (base : Nat) (live : Nat → Bool) :
{Γ : List Ty} → {t : Ty} → Expr S Γ t → Program
| _, _, .var x => x.ownedCode h slots base live
| _, _, _ => []
/-- Whether argument `i` of a call moves its variable into the call: the parameter is owned, and
the argument is an owned variable that is dead after the call and that no other argument with
arrays reads. -/
def callMoves (slots : List Slot) (live : Nat → Bool) {Γ : List Ty} {g : Sig}
(args : (i : Fin g.params.length) → Expr S Γ (g.params.get i)) (i : Fin g.params.length) :
Bool :=
g.mode i == .owned &&
match (args i).varIndex? with
| some k => (slots.getD k default).mode == .owned && !live k &&
!(argsAny fun j => j != i && !(g.params.get j).scalar && (args j).usesFast k)
| none => false
/-- The instructions that push the words of a variable or a pair of such expressions, read in
place. -/
def Expr.placeCode (h : Nat) (slots : List Slot) :
{Γ : List Ty} → {t : Ty} → Expr S Γ t → Program
| _, _, .var (t := t) x => loadCode h (slots.getD x.index default).loc t.types
| _, _, .pair first second => first.placeCode h slots ++ second.placeCode h slots
| _, _, _ => []
def TreeShape.slots (shape : TreeShape) (r : Fin shape.records) (base : Nat) (mode : Mode)
(rest : List Slot) : List Slot :=
⟨base, mode⟩ :: ((List.range (shape.words r)).map (fun i => ⟨base + 1 + i, .borrowed⟩) ++
((List.range (shape.children r)).map (fun i => ⟨base + 1 + shape.words r + i, mode⟩) ++
((List.range (shape.arrays r)).map
(fun i => ⟨base + 1 + shape.words r + shape.children r + i, mode⟩) ++
((List.range (shape.buffers r)).map (fun i =>
⟨base + 1 + shape.words r + shape.children r + shape.arrays r + 2 * i, mode⟩) ++
rest))))
/-- Code that runs `branch r` for the record constructor `r` of the record at local `ptr`: tests
of its tag against the record constructors in order, the last one untested. For an untagged
shape it is the single record constructor's code. -/
def TreeShape.dispatchCode (shape : TreeShape) (ptr : Nat) (branch : Fin shape.records → Program) :
Program :=
dispatchChain (treeTagCode ptr) (shape.tagPairs branch)
/-- The slots of parameters of types `ts` for which the modes `ms` were chosen: parameter `i` starts
after the words of the parameters before it. -/
def paramSlots : List Ty → List Mode → Nat → List Slot
| [], _, _ => []
| t :: ts, ms, loc =>
⟨loc, t.paramMode (ms.headD .borrowed)⟩ :: paramSlots ts ms.tail (loc + t.width)
def storeParamsCode (h base : Nat) : List Ty → Program
| [] => []
| t :: ts => storeParamsCode h (base + t.width) ts ++ storeCode h base t.types
/-- The locals that an expression needs from its first free local on: the words of each `letE`
and `letPair` value and of each `ite` result, two for each division or remainder, the count, the
index, and the state of each loop, the position of each read, the count, the address, and the
index of each `build`, with the element's words and the position of its first word, the
position, the value's words, the address, and the position of the first word of each `set`, the
value's words and the scratch of each `push` and `append`, and the locals of each copy, on a path
of the expression. A call's arguments, a pair's components, and a tuple's components leave their
words on the stack, so they share their locals. -/
def Expr.width : {Γ : List Ty} → {t : Ty} → Expr S Γ t → Nat
| _, _, .word _ | _, _, .bool _ | _, _, .size _ | _, _, .float _ | _, _, .float32 _
| _, _, .leSize _ _ => 0
| _, _, .bytesEmpty | _, _, .bytesSize _ => 0
| _, _, .bytesLiteral bytes => if bytes = [] then 0 else 4
| _, _, .bytesGet _ index => max index.width 1
| _, _, .var (t := t) _ => t.copyScratch
| _, _, .bin op left right => max op.scratch (max left.width right.width)
| _, _, .cmp _ left right | _, _, .and left right | _, _, .or left right
| _, _, .fbin _ left right | _, _, .fcmp _ left right
| _, _, .fbin32 _ left right | _, _, .fcmp32 _ left right =>
max left.width right.width
| _, _, .not e | _, _, .funary _ e | _, _, .funary32 _ e => e.width
| _, _, .toFloat _ e | _, _, .toFloat32 _ e => e.width
| _, _, .toWord _ e | _, _, .toWord32 _ e => e.width
| _, _, .ite (t := t) c thenE elseE =>
max c.width (t.width + max (max thenE.width elseE.width) (copyWidth t))
| _, _, .letE (s := s) value body => s.width + max value.width body.width
| _, _, .call (g := g) _ args =>
argsMax fun i => if g.mode i = .owned then copyWidth (g.params.get i) else (args i).width
| _, _, .pair (s := s) (t := t) first second =>
max (max first.width second.width) (max (copyWidth s) (copyWidth t))
| _, _, .letPair (s := s) (t := t) e body => s.width + t.width + max e.width body.width
| _, _, .loop (t := t) count init cond body =>
max count.width (max (1 + max init.width (copyWidth t))
(2 + t.width + max (max cond.width body.width) (copyWidth t)))
| _, _, .fold (e := e) (t := t) _ init body =>
max (1 + max init.width (copyWidth t))
(2 + t.width + max (e.width + max 2 body.width) (copyWidth t))
| _, _, .listFold (e := e) (captures := captures) args body =>
max (argsMax fun i => (args i).width)
(widthSum (ListFold.params e captures) + e.width + 3 +
max treeCopyScratch (e.width + max body.width treeCopyScratch))
| _, _, .get _ i => max i.width 2
| _, _, .build (e := e) count elem => max count.width (3 + max elem.width (e.width + 1))
| _, _, .bytesBuild count elem => max count.width (3 + elem.width)
| _, _, .set (e := e) _ i v =>
max i.width (max (1 + v.width) (1 + e.width + copyWidth (.array e)))
| _, _, .push (e := e) _ v => max v.width (e.width + 7)
| _, _, .append _ _ => 7
| _, _, .insertAt (e := e) _ i v => max i.width (max (1 + v.width) (e.width + 8))
| _, _, .eraseAt _ i => max i.width 8
| _, _, .mk first second => max first.width second.width
| _, _, .proj _ _ => 0
| _, _, .treeNil _ _ => 0
| _, _, .treeNode (shape := shape) r _ words arrays buffers children =>
shape.bindWidth r + max (argsMax fun i => (words i).width)
(max (argsMax fun i => max (children i).width (copyWidth (.tree shape)))
(max (argsMax fun i => max (arrays i).width (copyWidth (shape.arrayTy r i)))
(argsMax fun i => max (buffers i).width (copyWidth .bytes))))
| _, _, .treeCase (shape := shape) (t := t) _ empty nodes =>
t.width + max (max (if h : shape.hasNull = true then (empty h).width else 0) (copyWidth t))
(argsMax fun r => shape.bindWidth r + max treeCopyScratch (max (nodes r).width (copyWidth t)))
def Var.sizeCode (slots : List Slot) (live : Nat → Bool) {Γ : List Ty} {e : Elem}
(x : Var Γ (.array e)) : Program :=
[.localGet (slots.getD x.index default).loc, .wrapI64, .load64 0] ++ divCode e.width ++
(if (slots.getD x.index default).mode = .owned ∧ live x.index = false then
releaseCode (.array e) (slots.getD x.index default).loc else [])
def getCode (slots : List Slot) (base : Nat) (live : Nat → Bool) {Γ : List Ty} {e : Elem}
(x : Var Γ (.array e)) (indexCode : (Nat → Bool) → Program) : Program :=
indexCode (fun j => live j || j == x.index) ++
[.localSet base, .localGet base, .localGet (slots.getD x.index default).loc, .wrapI64,
.load64 0] ++ divCode e.width ++
[.ltUI64, .extendUI32, .localSet (base + 1), .localGet base] ++ scaleCode e.width ++
[.localSet base] ++
loadWordsCode (slots.getD x.index default).loc base (base + 1) 0 e.types ++
(if (slots.getD x.index default).mode = .owned ∧ live x.index = false then
releaseCode (.array e) (slots.getD x.index default).loc else [])
def loopCode (h : Nat) (slots : List Slot) (base : Nat) (live : Nat → Bool)
{Γ : List Ty} {t : Ty} (init : Expr S Γ t) (cond : Expr S (t :: Γ) .bool)
(body : Expr S (t :: .word :: Γ) t)
(countCode initCode condCode bodyCode : List Slot → Nat → (Nat → Bool) → Program) : Program :=
let modes := slots.map Slot.mode
let mode := (init.mode modes).join (body.mode (.borrowed :: .borrowed :: modes))
let all := fun i => live i || cond.usesFast (i + 1) || body.usesFast (i + 2)
countCode slots base (fun i => all i || init.usesFast i) ++
[.localSet base] ++ initCode slots (base + 1) all ++
coerceCode h t (init.mode modes) mode (base + 1) ++
storeCode h (base + 2) t.types ++ [.constI64 0, .localSet (base + 1),
.block 0 0 [.loop 0 0 ([.localGet (base + 1), .localGet base, .geUI64, .br_if 1] ++
condCode (⟨base + 2, .borrowed⟩ :: slots) (base + 2 + t.width)
(fun j => j == 0 || shift 1 all j) ++ [.eqzI64, .br_if 1] ++
(if mode = .owned ∧ body.usesFast 0 = false then releaseCode t (base + 2) else []) ++
bodyCode (⟨base + 2, mode⟩ :: ⟨base + 1, .borrowed⟩ :: slots)
(base + 2 + t.width) (shift 2 all) ++
coerceCode h t (body.mode (mode :: .borrowed :: modes)) mode (base + 2 + t.width) ++
storeCode h (base + 2) t.types ++
[.localGet (base + 1), .constI64 1, .addI64, .localSet (base + 1), .br 0]) [] []]
[] []] ++
releaseWhere Γ slots (fun i => (cond.usesFast (i + 1) || body.usesFast (i + 2)) && !live i) ++
loadCode h (base + 2) t.types
def tailSig (params : List Ty) (result : Ty) (modes : List Mode) : Sig :=
⟨params, result, true, modes, false⟩
def TailBody.codeWith
(exprCode : {Γ : List Ty} → {t : Ty} → Expr S Γ t →
List Slot → Nat → (Nat → Bool) → Program) (targetModes : List Mode) (h p0 resultBase : Nat) :
{Γ : List Ty} → TailBody S params result Γ → List Slot → Nat → Nat → Nat → Program
| _, .ret value, slots, base, exitLabel, _ =>
exprCode value slots base (fun _ => false) ++
coerceCode h result (value.mode (slots.map Slot.mode)) .owned base ++
storeCode h resultBase result.types ++ [.br exitLabel]
| Γ, .next args, slots, base, _, nextLabel =>
let g := tailSig params result targetModes
let all := fun i => argsAny fun j => (args j).usesFast i
let kept := fun i =>
all i && !(argsAny fun j => callMoves slots (fun _ => false) (g := g) args j &&
(args j).usesFast i)
argsCode (fun i =>
if (params.get i).scalar then exprCode (args i) slots base all
else if paramMode params targetModes i = .owned then (args i).ownedCode h slots base kept
else (args i).placeCode h slots) ++
releaseWhere Γ slots kept ++ storeParamsCode h p0 params ++ [.br nextLabel]
| Γ, .ite cond yes no, slots, base, exitLabel, nextLabel =>
let mid := fun i => yes.usesFast i || no.usesFast i
exprCode cond slots base mid ++
[.eqzI64, .iff 0 0
(releaseWhere Γ slots (fun i => mid i && !no.usesFast i) ++
no.codeWith exprCode targetModes h p0 resultBase slots base (exitLabel + 1) (nextLabel + 1))
(releaseWhere Γ slots (fun i => mid i && !yes.usesFast i) ++
yes.codeWith exprCode targetModes h p0 resultBase slots base (exitLabel + 1) (nextLabel + 1)) [] []]
| _, .letE (t := t) value body, slots, base, exitLabel, nextLabel =>
let mode := value.mode (slots.map Slot.mode)
exprCode value slots (base + t.width) (fun i => body.usesFast (i + 1)) ++
storeCode h base t.types ++
(if mode = .owned ∧ body.usesFast 0 = false then releaseCode t base else []) ++
body.codeWith exprCode targetModes h p0 resultBase (⟨base, mode⟩ :: slots) (base + t.width)
exitLabel nextLabel
| _, .letPair (s := s) (t := t) value body, slots, base, exitLabel, nextLabel =>
let mode := value.mode (slots.map Slot.mode)
exprCode value slots (base + s.width + t.width) (fun i => body.usesFast (i + 2)) ++
storeCode h (base + s.width) t.types ++ storeCode h base s.types ++
(if mode = .owned ∧ body.usesFast 1 = false then releaseCode s base else []) ++
(if mode = .owned ∧ body.usesFast 0 = false then releaseCode t (base + s.width) else []) ++
body.codeWith exprCode targetModes h p0 resultBase (⟨base + s.width, mode⟩ :: ⟨base, mode⟩ :: slots)
(base + s.width + t.width) exitLabel nextLabel
| Γ, .treeCase (shape := shape) x empty nodes, slots, base, exitLabel, nextLabel =>
let rootMode := (slots.getD x.index default).mode
let emptyUses := fun i => if hn : shape.hasNull = true then (empty hn).usesFast i else false
let nodeLive := fun r i => (nodes r).usesFast (i + shape.bindCount r)
let mid := fun i => decide (i = x.index) || emptyUses i || argsAny fun r => nodeLive r i
let depth := fun r : Fin shape.records => if r.val + 1 < shape.records then r.val + 1 else r.val
let labels := (if shape.hasNull then 1 else 0)
let nodeCode := fun r : Fin shape.records =>
releaseWhere Γ slots (fun i => mid i && !(nodeLive r i || decide (i = x.index))) ++
x.code h slots (base + shape.bindWidth r) (nodeLive r) ++ [.localSet base] ++
treeLoadFields h base (shape.wordTypes r) (base + 1)
(base + 1 + shape.words r) shape.fieldStart (shape.fields r) ++
treeLoadHeaps base (shape.kinds r) (base + 1 + shape.words r + shape.children r)
(base + 1 + shape.words r + shape.children r + shape.arrays r)
(shape.fieldStart + (shape.fields r).length) ++
(if rootMode = .owned then
LeanExe.Runtime.headerStore base 8 [.constI64 0] else []) ++
releaseWhere (shape.context r Γ) (shape.slots r base rootMode slots)
(fun i => decide (i < shape.bindCount r) && !(nodes r).usesFast i) ++
(nodes r).codeWith exprCode targetModes h p0 resultBase (shape.slots r base rootMode slots)
(base + shape.bindWidth r) (exitLabel + labels + depth r) (nextLabel + labels + depth r)
let recordsCode := shape.dispatchCode (slots.getD x.index default).loc nodeCode
if hn : shape.hasNull = true then
[.localGet (slots.getD x.index default).loc, .eqzI64, .iff 0 0
(releaseWhere Γ slots (fun i => mid i && !emptyUses i) ++
(empty hn).codeWith exprCode targetModes h p0 resultBase slots base (exitLabel + 1)
(nextLabel + 1))
recordsCode [] []]
else recordsCode
def ListFold.leafCode {S : List Sig} (h : Nat)
(bodyCode : List Slot → Nat → (Nat → Bool) → Program)
{Γ : List Ty} {t : Ty} (value : Expr S Γ t) (slots : List Slot)
(base : Nat) (live : Nat → Bool) : Program :=
match value with
| .var x => x.code h slots base live
| _ => bodyCode slots base live
def ListFold.loopCode {S : List Sig} {e : Elem} {captures : List Ty}
(body : Expr S (ListFold.context e captures) (.elem e)) (h stateBase : Nat)
(bodyCode : List Slot → Nat → (Nat → Bool) → Program) : Program :=
let stateSlots := paramSlots (ListFold.params e captures) [] stateBase
let resultBase := stateBase + widthSum (ListFold.params e captures)
let tail := ListFold.tailBody body
[.block 0 0 [.loop 0 0
(releaseWhere (ListFold.params e captures) stateSlots (fun i => !tail.usesFast i) ++
tail.codeWith (ListFold.leafCode h bodyCode) [] h stateBase resultBase stateSlots
(resultBase + e.width) 1 0)
[] []] [] []]
/-- The instructions that push the words that hold the value of an expression. Variable `x`
starts at position `(slots.getD x.index default).loc`, the positions from `base` on are free, `h`
is the number of positions, which places each position's f64 local, and `live` gives the
variables live after the expression, which each subexpression receives together with those that
the rest of the expression reads. Each expression consumes the owned variables
that die in it: a variable moves where it dies and is copied where it stays live, a reader
releases a dying variable it reads, and a branch, a binding, and a loop release the owned
variables that die without a use. A comparison widens its 32-bit result to a word. `ite` tests
its condition with `i64.eqz`, so its `if` runs the else branch first, and each branch stores its
words in the locals from `base` on, which the code loads after the `if`: the encoder writes block
types of at most one result. `letE` stores its value from local `base` on and gives its body the
locals above it, and `letPair` stores its first component from `base` on and its second after
it. A call pushes its arguments in order, its arrays read in place, and calls the function. A
loop keeps its count in local `base`, its index in local `base + 1`, and its state from local
`base + 2` on. Each pass leaves the block when the index reaches the count or when the
condition, which reads the state as a borrowed variable, is false. The outer variables that the
condition or the body uses stay live through the loop, and the owned ones not live after it are
released there. `size` loads
the length word at the array's address and divides it by the element's word count `k`. `get`
keeps the position in local `base`, compares it with the size, keeps the result as a flag in local
`base + 1`, puts the position of the element's first word in local `base`, and loads each of the
element's words under the flag, 0 past the end, turning a float's word into an f64. `build` keeps
its count in local `base`, traps at `unreachable` when the count's words would be `2 ^ 29` or
more, since the array would not fit in 32-bit memory, allocates the array into local `base + 1`,
and keeps the index in local `base + 2`. For each index it runs the element's code, stores the
element's words from local `base + 3` on, puts the position of the element's first word after them,
and writes each word; the outer variables that only the element reads stay live through the loop
and are released after it. `set` keeps the position in local `base` and the value's words from
local `base + 1` on, then takes the array as owned, in local `base + 1 + k`, and writes the
element's words when the position is below the size: an owned array that dies there is updated in
its own block, and any other is copied first. `push` keeps the value's words from local `base` on
and their count in local `base + k`, and `append` keeps the length of `y` in local `base`; both
take the array `x` with `Var.roomCode`, which gives a block with room for the result, write the new
words, and push the block's address. `append` releases `y` after the copy when `y` is owned and
dies there. `insertAt` keeps the position in local `base`, the value's words from local
`base + 1` on, and their count after them, takes the array with `Var.roomCode` for one more
element, and then compares the position with the old size. In range, it moves the words from the
element's first word, which it keeps in the scratch local of the new length, to the old length up
by the element's word count with `shiftUpCode` and writes the value's words there. Out of range,
it writes the old length back: `Var.roomCode` has then copied or moved the array, and it traps when
the longer length would be `2 ^ 29` words or more, where Lean returns the array unchanged.
`eraseAt` keeps the position in local `base`, the array as owned in local `base + 1`, its length
in local `base + 2`, and the element's first word in local `base + 3`. When the position is below
the size, it moves the words after the element down by the element's word count with
`copyIntoCode`, whose source and destination lie in the same block, and writes the shorter length.
`mk` pushes its components' words in order, and `proj` loads the words of the
component from the tuple variable's positions. -/
def Expr.code (h : Nat) (slots : List Slot) (base : Nat) (live : Nat → Bool)
(pool : Bytes.LiteralPool) :
{Γ : List Ty} → {t : Ty} → Expr S Γ t → Program
| _, _, .word value => [.constI64 value]
| _, _, .bytesEmpty => [.constI64 0, .constI64 0]
| _, _, .bytesLiteral bytes =>
if bytes = [] then [.constI64 0, .constI64 0] else
match Bytes.literalOffset pool.values bytes with
| some offset => Bytes.literalCode (UInt64.ofNat (pool.base + offset)) bytes base
| none => [.unreachable]
| _, _, .bytesSize x =>
[.localGet ((slots.getD x.index default).loc + 1)] ++
(if (slots.getD x.index default).mode = .owned ∧ live x.index = false then
releaseCode .bytes (slots.getD x.index default).loc else [])
| _, _, .bytesGet x index =>
index.code (pool := pool) h slots base (fun i => live i || i == x.index) ++ [.localSet base] ++
Bytes.getCode (slots.getD x.index default).loc
((slots.getD x.index default).loc + 1) base ++
(if (slots.getD x.index default).mode = .owned ∧ live x.index = false then
releaseCode .bytes (slots.getD x.index default).loc else [])
| Γ, _, .bytesBuild count elem =>
let all := fun i => live i || elem.usesFast (i + 1)
count.code (pool := pool) h slots base all ++ [.localSet base] ++
Bytes.buildCode base (base + 1) (base + 2)
(elem.code (pool := pool) h (⟨base + 2, .borrowed⟩ :: slots) (base + 3) (shift 1 all)) ++
releaseWhere Γ slots (fun i => elem.usesFast (i + 1) && !live i)
| _, _, .treeNil _ _ => [.constI64 0]
| _, _, .treeNode (shape := shape) r reuse words arrays buffers children =>
let modes := slots.map Slot.mode
let scratch := base + shape.bindWidth r
let kept := fun k => live k || optionVarUses reuse k
argsCode (fun i =>
(words i).code (pool := pool) h slots scratch (fun k => kept k ||
(argsAny fun j => decide (i.val < j.val) && (words j).usesFast k) ||
(argsAny fun j => (children j).usesFast k) || (argsAny fun j => (arrays j).usesFast k) ||
(argsAny fun j => (buffers j).usesFast k)) ++
storeCode h (base + 1 + i.val) (shape.wordTy r i).types) ++
argsCode (fun i =>
(children i).code (pool := pool) h slots scratch (fun k => kept k ||
(argsAny fun j => decide (i.val < j.val) && (children j).usesFast k) ||
(argsAny fun j => (arrays j).usesFast k) || (argsAny fun j => (buffers j).usesFast k)) ++
coerceCode h (.tree shape) ((children i).mode modes) .owned scratch ++
[.localSet (base + 1 + shape.words r + i.val)]) ++
argsCode (fun i =>
(arrays i).code (pool := pool) h slots scratch (fun k => kept k ||
(argsAny fun j => decide (i.val < j.val) && (arrays j).usesFast k) ||
(argsAny fun j => (buffers j).usesFast k)) ++
coerceCode h (shape.arrayTy r i) ((arrays i).mode modes) .owned scratch ++
[.localSet (base + 1 + shape.words r + shape.children r + i.val)]) ++
argsCode (fun i =>
(buffers i).code (pool := pool) h slots scratch (fun k => kept k ||
(argsAny fun j => decide (i.val < j.val) && (buffers j).usesFast k)) ++
coerceCode h .bytes ((buffers i).mode modes) .owned scratch ++
storeCode h (base + 1 + shape.words r + shape.children r + shape.arrays r + 2 * i.val)
Ty.bytes.types ++
Bytes.normalizeCode (base + 1 + shape.words r + shape.children r + shape.arrays r + 2 * i.val)
(base + 1 + shape.words r + shape.children r + shape.arrays r + 2 * i.val + 1)) ++
(match reuse with
| some x =>
if (slots.getD x.index default).mode = .owned ∧ live x.index = false then
[.localGet (slots.getD x.index default).loc, .localSet base]
else [.constI64 (UInt64.ofNat (8 * shape.width r)), .call 0, .localSet base]
| none => [.constI64 (UInt64.ofNat (8 * shape.width r)), .call 0, .localSet base]) ++
shape.tagStoreCode r base ++
treeStoreFields h base (shape.wordTypes r) (base + 1) (base + 1 + shape.words r)
shape.fieldStart (shape.fields r) ++
treeStoreHeaps base (shape.kinds r) (base + 1 + shape.words r + shape.children r)
(base + 1 + shape.words r + shape.children r + shape.arrays r)
(shape.fieldStart + (shape.fields r).length) ++
LeanExe.Runtime.headerStore base 24 [.constI64 1] ++
LeanExe.Runtime.headerStore base 16 [.constI64 (UInt64.ofNat (shape.width r))] ++
LeanExe.Runtime.headerStore base 8 [.constI64 (shape.mask r)] ++ [.localGet base]
| Γ, _, .treeCase (shape := shape) (t := t) x empty nodes =>
let modes := slots.map Slot.mode
let rootMode := (slots.getD x.index default).mode
let nodeModes := fun r => shape.modes r rootMode modes
let emptyMode := if hn : shape.hasNull = true then (empty hn).mode modes else .borrowed
let resultMode := recordsJoin (fun r => (nodes r).mode (nodeModes r)) emptyMode
let fieldBase := base + t.width
let emptyUses := fun i => if hn : shape.hasNull = true then (empty hn).usesFast i else false
let nodeUses := fun r i => (nodes r).usesFast (i + shape.bindCount r)
let mid := fun i => live i || decide (i = x.index) || emptyUses i ||
argsAny fun r => nodeUses r i
let nodeCode := fun r : Fin shape.records =>
let scratch := fieldBase + shape.bindWidth r
releaseWhere Γ slots
(fun i => mid i && !((live i || nodeUses r i) || decide (i = x.index))) ++
x.code h slots scratch (fun i => live i || nodeUses r i) ++ [.localSet fieldBase] ++
treeLoadFields h fieldBase (shape.wordTypes r) (fieldBase + 1)
(fieldBase + 1 + shape.words r) shape.fieldStart (shape.fields r) ++
treeLoadHeaps fieldBase (shape.kinds r) (fieldBase + 1 + shape.words r + shape.children r)
(fieldBase + 1 + shape.words r + shape.children r + shape.arrays r)
(shape.fieldStart + (shape.fields r).length) ++
(if rootMode = .owned then
LeanExe.Runtime.headerStore fieldBase 8 [.constI64 0] else []) ++
releaseWhere (shape.context r Γ) (shape.slots r fieldBase rootMode slots)
(fun i => decide (i < shape.bindCount r) && !(nodes r).usesFast i) ++
(nodes r).code (pool := pool) h (shape.slots r fieldBase rootMode slots) scratch
(shift (shape.bindCount r) live) ++
coerceCode h t ((nodes r).mode (nodeModes r)) resultMode scratch ++ storeCode h base t.types
let recordsCode := shape.dispatchCode (slots.getD x.index default).loc nodeCode
(if hn : shape.hasNull = true then
[.localGet (slots.getD x.index default).loc, .eqzI64, .iff 0 0
(releaseWhere Γ slots (fun i => mid i && !(live i || emptyUses i)) ++
(empty hn).code (pool := pool) h slots fieldBase live ++
coerceCode h t ((empty hn).mode modes) resultMode fieldBase ++ storeCode h base t.types)
recordsCode [] []]
else recordsCode) ++ loadCode h base t.types
| _, _, .bool value => [.constI64 (boolWord value)]
| _, _, .float32 bits =>
[.f32Const (if Wasm.IEEE32.isNaN bits then Wasm.IEEE32.canonicalNaN else bits)]
| _, _, .float bits =>
[.f64Const (if Wasm.IEEE64.isNaN bits then Wasm.IEEE64.canonicalNaN else bits)]
| _, _, .fbin op left right =>
left.code (pool := pool) h slots base (fun i => live i || right.usesFast i) ++ right.code (pool := pool) h slots base live ++
[op.instr]
| _, _, .funary op e => op.code (e.code (pool := pool) h slots base live)
| _, _, .fcmp op left right =>
left.code (pool := pool) h slots base (fun i => live i || right.usesFast i) ++ right.code (pool := pool) h slots base live ++
[op.instr, .extendUI32]
| _, _, .toFloat op e => e.code (pool := pool) h slots base live ++ op.code
| _, _, .toWord op e => e.code (pool := pool) h slots base live ++ [op.instr]
| _, _, .fbin32 op left right =>
left.code (pool := pool) h slots base (fun i => live i || right.usesFast i) ++ right.code (pool := pool) h slots base live ++
[op.instr32]
| _, _, .funary32 op e => op.code (e.code (pool := pool) h slots base live)
| _, _, .fcmp32 op left right =>
left.code (pool := pool) h slots base (fun i => live i || right.usesFast i) ++ right.code (pool := pool) h slots base live ++
[op.instr32, .extendUI32]
| _, _, .toFloat32 op e => e.code (pool := pool) h slots base live ++ op.code
| _, _, .toWord32 op e => e.code (pool := pool) h slots base live ++ op.code
| _, _, .var x => x.code h slots base live
| _, _, .bin op left right =>
left.code (pool := pool) h slots base (fun i => live i || right.usesFast i) ++ right.code (pool := pool) h slots base live ++
op.code base
| _, _, .cmp op left right =>
left.code (pool := pool) h slots base (fun i => live i || right.usesFast i) ++ right.code (pool := pool) h slots base live ++
[op.instr, .extendUI32]
| _, _, .not e => e.code (pool := pool) h slots base live ++ [.eqzI64, .extendUI32]
| _, _, .and left right =>
left.code (pool := pool) h slots base (fun i => live i || right.usesFast i) ++ right.code (pool := pool) h slots base live ++
[.andI64]
| _, _, .or left right =>
left.code (pool := pool) h slots base (fun i => live i || right.usesFast i) ++ right.code (pool := pool) h slots base live ++
[.orI64]
| Γ, _, .ite (t := t) c thenE elseE =>
let modes := slots.map Slot.mode
let mode := (thenE.mode modes).join (elseE.mode modes)
let mid := fun i => live i || thenE.usesFast i || elseE.usesFast i
c.code (pool := pool) h slots base mid ++
[.eqzI64, .iff 0 0
(releaseWhere Γ slots (fun i => mid i && !(live i || elseE.usesFast i)) ++
elseE.code (pool := pool) h slots (base + t.width) live ++
coerceCode h t (elseE.mode modes) mode (base + t.width) ++ storeCode h base t.types)
(releaseWhere Γ slots (fun i => mid i && !(live i || thenE.usesFast i)) ++
thenE.code (pool := pool) h slots (base + t.width) live ++
coerceCode h t (thenE.mode modes) mode (base + t.width) ++ storeCode h base t.types)
[] []] ++
loadCode h base t.types
| _, _, .letE (s := s) value body =>
let mode := value.mode (slots.map Slot.mode)
value.code (pool := pool) h slots (base + s.width) (fun i => live i || body.usesFast (i + 1)) ++
storeCode h base s.types ++
(if mode = .owned ∧ body.usesFast 0 = false then releaseCode s base else []) ++
body.code (pool := pool) h (⟨base, mode⟩ :: slots) (base + s.width) (shift 1 live)
| Γ, _, .call (g := g) f args =>
let all := fun i => live i || argsAny fun j => (args j).usesFast i
let kept := fun i => all i && !(argsAny fun j => callMoves slots live args j && (args j).usesFast i)
(if g.depth then [.localGet 0, .constI64 1, .addI64] else []) ++
argsCode (fun i =>
if (g.params.get i).scalar then (args i).code (pool := pool) h slots base all
else if g.mode i = .owned then (args i).ownedCode h slots base kept
else (args i).placeCode h slots) ++
[.call f.callIndex] ++
releaseWhere Γ slots (fun i => kept i && !live i)
| _, _, .pair (s := s) (t := t) first second =>
let modes := slots.map Slot.mode
let mode := (first.mode modes).join (second.mode modes)
first.code (pool := pool) h slots base (fun i => live i || second.usesFast i) ++
coerceCode h s (first.mode modes) mode base ++
second.code (pool := pool) h slots base live ++ coerceCode h t (second.mode modes) mode base
| _, _, .letPair (s := s) (t := t) e body =>
let mode := e.mode (slots.map Slot.mode)
e.code (pool := pool) h slots (base + s.width + t.width) (fun i => live i || body.usesFast (i + 2)) ++
storeCode h (base + s.width) t.types ++ storeCode h base s.types ++
(if mode = .owned ∧ body.usesFast 1 = false then releaseCode s base else []) ++
(if mode = .owned ∧ body.usesFast 0 = false then releaseCode t (base + s.width) else []) ++
body.code (pool := pool) h (⟨base + s.width, mode⟩ :: ⟨base, mode⟩ :: slots) (base + s.width + t.width)
(shift 2 live)
| _, _, .loop count init cond body =>
loopCode h slots base live init cond body
(fun slots base live => count.code (pool := pool) h slots base live)
(fun slots base live => init.code (pool := pool) h slots base live)
(fun slots base live => cond.code (pool := pool) h slots base live)
(fun slots base live => body.code (pool := pool) h slots base live)
| Γ, _, .fold (e := e) (t := t) x init body =>
loopCode h slots base live init (.bool true) (Expr.foldStep x body)
(fun slots _ live => x.sizeCode slots live)
(fun slots base live => init.code (pool := pool) h slots base live)
(fun _ _ _ => [.constI64 1])
(fun slots base live =>
getCode (Γ := t :: .word :: Γ) slots (base + e.width)
(fun i => live i || body.usesFast (i + 1))
(.there (.there x))
(fun live => Var.code (Γ := t :: .word :: Γ) (t := .word)
h slots (base + e.width) live (.there .here)) ++
storeCode h base e.types ++
body.code (pool := pool) h (⟨base, .borrowed⟩ :: slots) (base + e.width) (shift 1 live))
| Γ, _, .listFold (e := e) (captures := captures) args body =>
let all := fun i => live i || argsAny fun j => (args j).usesFast i
argsCode (fun i =>
if ((ListFold.params e captures).get i).scalar then (args i).code (pool := pool) h slots base all
else (args i).placeCode h slots) ++
storeParamsCode h base (ListFold.params e captures) ++
ListFold.loopCode body h base (fun slots base live => body.code (pool := pool) h slots base live) ++
releaseWhere Γ slots (fun i => all i && !live i) ++
loadCode h (base + widthSum (ListFold.params e captures)) e.types
| _, _, .size x => x.sizeCode slots live
| _, _, .leSize x i =>
i.code h slots base (fun j => live j || j == x.index) ++
x.sizeCode slots live ++ [.leUI64, .extendUI32]
| _, _, .get x i => getCode slots base live x (fun live => i.code (pool := pool) h slots base live)
| _, _, .set (e := e) x i v =>
i.code (pool := pool) h slots base (fun j => live j || j == x.index || v.usesFast j) ++ [.localSet base] ++
v.code (pool := pool) h slots (base + 1) (fun j => live j || j == x.index) ++
storeCode h (base + 1) e.types ++ x.ownedCode h slots (base + 1 + e.width) live ++
[.localSet (base + 1 + e.width), .localGet base, .localGet (base + 1 + e.width), .wrapI64,
.load64 0] ++ divCode e.width ++
[.ltUI64, .iff 0 0 ([.localGet base] ++ scaleCode e.width ++
[.localSet (base + 2 + e.width)] ++
storeWordsCode h (base + 1 + e.width) (base + 2 + e.width) 0 (base + 1) e.types) [] [] [],
.localGet (base + 1 + e.width)]
| _, _, .push (e := e) x v =>
v.code (pool := pool) h slots base (fun j => live j || j == x.index) ++ storeCode h base e.types ++
[.constI64 (wordCount e.width), .localSet (base + e.width)] ++
x.roomCode slots (base + e.width + 1) live (base + e.width) ++
storeWordsCode h (base + e.width + 5) (base + e.width + 2) 0 base e.types ++
[.localGet (base + e.width + 5)]
| Γ, _, .append x y =>
[.localGet (slots.getD y.index default).loc, .wrapI64, .load64 0, .localSet base] ++
x.roomCode slots (base + 1) (fun k => live k || k == y.index) base ++
[.localGet (base + 5), .localGet (base + 2), .constI64 8, .mulI64, .addI64,
.localSet (base + 4)] ++
copyIntoCode (slots.getD y.index default).loc (base + 4) base (base + 6) ++
releaseWhere Γ slots (fun k => k == y.index && !live k) ++ [.localGet (base + 5)]
| _, _, .insertAt (e := e) x i v =>
i.code (pool := pool) h slots base (fun j => live j || j == x.index || v.usesFast j) ++ [.localSet base] ++
v.code (pool := pool) h slots (base + 1) (fun j => live j || j == x.index) ++
storeCode h (base + 1) e.types ++
[.constI64 (wordCount e.width), .localSet (base + e.width + 1)] ++
x.roomCode slots (base + e.width + 2) live (base + e.width + 1) ++
[.localGet base, .localGet (base + e.width + 3)] ++ divCode e.width ++
[.leUI64, .iff 0 0 ([.localGet base] ++ scaleCode e.width ++
[.localSet (base + e.width + 4), .localGet (base + e.width + 3),
.localSet (base + e.width + 7)] ++
shiftUpCode (base + e.width + 6) (base + e.width + 4) (base + e.width + 7) e.width ++
storeWordsCode h (base + e.width + 6) (base + e.width + 4) 0 (base + 1) e.types)
[.localGet (base + e.width + 6), .wrapI64, .localGet (base + e.width + 3), .store64 0]
[] [],
.localGet (base + e.width + 6)]
| _, _, .eraseAt (e := e) x i =>
i.code (pool := pool) h slots base (fun j => live j || j == x.index) ++ [.localSet base] ++
x.ownedCode h slots (base + 1) live ++
[.localSet (base + 1), .localGet (base + 1), .wrapI64, .load64 0, .localSet (base + 2),
.localGet base, .localGet (base + 2)] ++ divCode e.width ++
[.ltUI64, .iff 0 0 ([.localGet base] ++ scaleCode e.width ++
[.localSet (base + 3), .localGet (base + 2), .localGet (base + 3), .subI64,
.constI64 (wordCount e.width), .subI64, .localSet (base + 4),
.localGet (base + 1), .localGet (base + 3), .constI64 (wordCount e.width), .addI64,
.constI64 8, .mulI64, .addI64, .localSet (base + 5),
.localGet (base + 1), .localGet (base + 3), .constI64 8, .mulI64, .addI64,
.localSet (base + 6)] ++
copyIntoCode (base + 5) (base + 6) (base + 4) (base + 7) ++
[.localGet (base + 1), .wrapI64, .localGet (base + 2), .constI64 (wordCount e.width),
.subI64, .store64 0]) [] [] [],
.localGet (base + 1)]
| Γ, _, .build (e := e) count elem =>
let all := fun i => live i || elem.usesFast (i + 1)
count.code (pool := pool) h slots base all ++
[.localSet base, .localGet base,
.constI64 (UInt64.ofNat ((536870912 + e.width - 1) / e.width)), .geUI64,
.iff 0 0 [.unreachable] [] [] [], .localGet base] ++ scaleCode e.width ++
[.localSet (base + 2)] ++ allocArrayCode (base + 2) (base + 1) ++
[.constI64 0, .localSet (base + 2),
.block 0 0 [.loop 0 0 ([.localGet (base + 2), .localGet base, .geUI64, .br_if 1] ++
elem.code (pool := pool) h (⟨base + 2, .borrowed⟩ :: slots) (base + 3) (shift 1 all) ++
storeCode h (base + 3) e.types ++ [.localGet (base + 2)] ++ scaleCode e.width ++
[.localSet (base + 3 + e.width)] ++
storeWordsCode h (base + 1) (base + 3 + e.width) 0 (base + 3) e.types ++
[.localGet (base + 2), .constI64 1, .addI64, .localSet (base + 2), .br 0])
[] []] [] []] ++
releaseWhere Γ slots (fun i => elem.usesFast (i + 1) && !live i) ++ [.localGet (base + 1)]
| _, _, .mk first second =>
first.code (pool := pool) h slots base (fun i => live i || second.usesFast i) ++ second.code (pool := pool) h slots base live
| _, _, .proj (e' := e') x p =>
loadCode h ((slots.getD x.index default).loc + p.offset) e'.types
/-- The parameter types of a function's code: its parameters' words, after the call depth when
the code takes it. -/
def codeParams (params : List Ty) (depth : Bool) : List ValueType :=
(if depth then [.i64] else []) ++ params.flatMap Ty.types
def Func.type (func : Func S) : FuncType :=
{ params := codeParams func.params func.depth
results := func.result.types }
def RecFunc.type (func : RecFunc S) : FuncType :=
{ params := codeParams func.params true
results := func.result.types }
/-- The instructions that copy each floating parameter word to its position's typed local. -/
def paramCopyCode (h : Nat) : Nat → List ValueType → Program
| _, [] => []
| p, .f64 :: tys => [.localGet p, .localSet (p + h)] ++ paramCopyCode h (p + 1) tys
| p, .f32 :: tys => [.localGet p, .localSet (p + 2 * h)] ++ paramCopyCode h (p + 1) tys
| p, _ :: tys => paramCopyCode h (p + 1) tys
/-- The call depth at which the code of a function that takes the depth traps at `unreachable`. -/
def depthLimit : UInt64 := 1000
/-- The test of a function that takes the call depth, at local 0: a trap at `unreachable` when it
has reached the limit. -/
def guardCode : Program :=
[.localGet 0, .constI64 depthLimit, .geUI64, .iff 0 0 [.unreachable] [] [] []]
/-- The positions of a function with parameters `params` and body `body`: the call depth when the
code takes it, the parameters' words, and the locals that the body needs. -/
def positions {S : List Sig} {params : List Ty} {result : Ty} (body : Expr S params result)
(depth : Bool) : Nat :=
(if depth then 1 else 0) + widthSum params + body.width + copyWidth result
/-- The code of a function with parameters `params`, whose modes `modes` chose, and body `body`:
the call depth when `depth` holds, then the words of the arguments, are its first positions, and
the positions that the body needs follow them. An i64 local for each remaining position and
f64 and f32 locals for every position follow the parameters. The code tests the depth, copies
floating parameters to their typed locals, releases the owned parameters that the body does not use, and runs
the body, which leaves the words of the result on the stack. -/
def bodyFunction {S : List Sig} {params : List Ty} {result : Ty} (modes : List Mode)
(body : Expr S params result) (depth : Bool) (typeIdx : Nat)
(pool : Bytes.LiteralPool) : Wasm.Function :=
let p0 := if depth then 1 else 0
let slots := paramSlots params modes p0
let h := positions body depth
{ params := codeParams params depth
locals := List.replicate (body.width + copyWidth result) .i64 ++
List.replicate h .f64 ++ List.replicate h .f32
body := (if depth then guardCode else []) ++
paramCopyCode h p0 (params.flatMap Ty.types) ++
releaseWhere params slots (fun i => !body.usesFast i) ++
body.code (pool := pool) h slots (p0 + widthSum params) (fun _ => false) ++
coerceCode h result (body.mode (slots.map Slot.mode)) .owned
(p0 + widthSum params + body.width)
results := result.types
typeIdx := some typeIdx }
def Func.function (func : Func S) (typeIdx : Nat)
(pool : Bytes.LiteralPool) : Wasm.Function :=
bodyFunction func.modes func.body func.depth typeIdx pool
def RecFunc.function (func : RecFunc S) (typeIdx : Nat)
(pool : Bytes.LiteralPool) : Wasm.Function :=
bodyFunction func.modes func.body true typeIdx pool
/-- The entry of a function whose code takes the call depth, exported under its name: it calls
the function at `idx` with depth 0 and its own arguments. -/
def entryFunction (params : List Ty) (result : Ty) (idx typeIdx : Nat) : Wasm.Function :=
let tys := params.flatMap Ty.types
{ params := tys
locals := []
body := [.constI64 0] ++ (List.range tys.length).map Instruction.localGet ++ [.call idx]
results := result.types
typeIdx := some typeIdx }
/-- Globals 0 through 3 hold the allocator state: the bump pointer, which starts at `top`, the
free-list head, and the allocation and free counters. Copied from `LeanExe.IR`, where `top` is
the heap base 4096. -/