aboutsummaryrefslogtreecommitdiff
path: root/src/Experiments/NewPipeline/CStringification.v
blob: c3644d3ec9d2227cc1d31a56424634a3233e6673 (plain)
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
Require Import Coq.ZArith.ZArith.
Require Import Coq.MSets.MSetPositive.
Require Import Coq.FSets.FMapPositive.
Require Import Coq.Strings.String.
Require Import Coq.Strings.Ascii.
Require Import Coq.Bool.Bool.
Require Import Crypto.Util.ListUtil Coq.Lists.List.
Require Crypto.Util.Strings.String.
Require Import Crypto.Util.Strings.Decimal.
Require Import Crypto.Util.Strings.HexString.
Require Import Crypto.Util.Strings.Show.
Require Import Crypto.Util.ZRange.
Require Import Crypto.Util.ZRange.Operations.
Require Import Crypto.Util.ZRange.Show.
Require Import Crypto.Util.Option.
Require Import Crypto.Experiments.NewPipeline.Language.
Require Import Crypto.Experiments.NewPipeline.AbstractInterpretation.
Require Import Crypto.Util.Bool.Equality.
Require Import Crypto.Util.Notations.
Import ListNotations. Local Open Scope zrange_scope. Local Open Scope Z_scope.

Module Compilers.
  Local Set Boolean Equality Schemes.
  Local Set Decidable Equality Schemes.
  Export Language.Compilers.
  Export AbstractInterpretation.Compilers.
  Import invert_expr.
  Import defaults.

  Module ToString.
    Local Open Scope string_scope.

    Module PHOAS.
      Module type.
        Module base.
          Global Instance show_base : Show base.type.base
            := fun _ t => match t with
                       | base.type.unit => "()"
                       | base.type.Z => "ℤ"
                       | base.type.bool => "𝔹"
                       | base.type.nat => "ℕ"
                       end.
          Fixpoint show_type (with_parens : bool) (t : base.type) : string
            := match t with
               | base.type.type_base t => show with_parens t
               | base.type.prod A B => maybe_wrap_parens
                                        with_parens
                                        (@show_type false A ++ " * " ++ @show_type true B)
               | base.type.list A => "[" ++ @show_type false A ++ "]"
               end.
          Fixpoint show_base_interp {t} : Show (base.base_interp t)
            := match t with
               | base.type.unit => @show unit _
               | base.type.Z => @show Z _
               | base.type.bool => @show bool _
               | base.type.nat => @show nat _
               end.
          Global Existing Instance show_base_interp.
          Fixpoint show_interp {t} : Show (base.interp t)
            := match t with
               | base.type.type_base t => @show (base.base_interp t) _
               | base.type.prod A B => @show (base.interp A * base.interp B) _
               | base.type.list A => @show (list (base.interp A)) _
               end.
          Global Existing Instance show_interp.
          Global Instance show : Show base.type := show_type.
        End base.
        Fixpoint show_type {base_type} {S : Show base_type} (with_parens : bool) (t : type.type base_type) : string
          := match t with
             | type.base t => S with_parens t
             | type.arrow s d
               => maybe_wrap_parens
                   with_parens
                   (@show_type base_type S true s ++ " → " ++ @show_type base_type S false d)
             end.
        Global Instance show {base_type} {S : Show base_type} : Show (type.type base_type) := show_type.
      End type.

      Module ident.
        Definition bitwidth_to_string (v : Z) : string
          := (if v =? 2^Z.log2 v then "2^" ++ decimal_string_of_Z (Z.log2 v) else HexString.of_Z v).
        Global Instance show_ident {t} : Show (ident.ident t)
          := fun with_parens idc
             => match idc with
               | ident.Literal t v => show with_parens v
               | ident.Nat_succ => "Nat.succ"
               | ident.Nat_pred => "Nat.pred"
               | ident.Nat_max => "Nat.max"
               | ident.Nat_mul => "Nat.mul"
               | ident.Nat_add => "Nat.add"
               | ident.Nat_sub => "Nat.sub"
               | ident.nil t => "[]"
               | ident.cons t => "(::)"
               | ident.pair A B => "(,)"
               | ident.fst A B => "fst"
               | ident.snd A B => "snd"
               | ident.pair_rect A B T => "pair_rect"
               | ident.bool_rect T => "bool_rect"
               | ident.nat_rect P => "nat_rect"
               | ident.list_rect A P => "list_rect"
               | ident.list_case A P => "list_case"
               | ident.List_length T => "length"
               | ident.List_seq => "seq"
               | ident.List_repeat A => "repeat"
               | ident.List_combine A B => "combine"
               | ident.List_map A B => "map"
               | ident.List_app A => "(++)"
               | ident.List_rev A => "rev"
               | ident.List_flat_map A B => "flat_map"
               | ident.List_partition A => "partition"
               | ident.List_fold_right A B => "fold_right"
               | ident.List_update_nth T => "update_nth"
               | ident.List_nth_default T => "nth_default"
               | ident.Z_add => "(+)"
               | ident.Z_mul => "( * )"
               | ident.Z_pow => "(^)"
               | ident.Z_sub => "(-)"
               | ident.Z_opp => "-"
               | ident.Z_div => "(/)"
               | ident.Z_modulo => "(mod)"
               | ident.Z_eqb => "(=)"
               | ident.Z_leb => "(≤)"
               | ident.Z_of_nat => "(ℕ→ℤ)"
               | ident.Z_shiftr offset => "(>> " ++ decimal_string_of_Z offset ++ ")"
               | ident.Z_shiftl offset => "(<< " ++ decimal_string_of_Z offset ++ ")"
               | ident.Z_land mask => "(& " ++ HexString.of_Z mask ++ ")"
               | ident.Z_mul_split => "Z.mul_split"
               | ident.Z_mul_split_concrete s => maybe_wrap_parens with_parens ("Z.mul_split " ++ bitwidth_to_string s)
               | ident.Z_add_get_carry => "Z.add_get_carry"
               | ident.Z_add_get_carry_concrete s => maybe_wrap_parens with_parens ("Z.add_get_carry " ++ bitwidth_to_string s)
               | ident.Z_add_with_carry => "Z.add_with_carry"
               | ident.Z_add_with_get_carry => "Z.add_with_get_carry"
               | ident.Z_add_with_get_carry_concrete s => maybe_wrap_parens with_parens ("Z.add_with_get_carry " ++ bitwidth_to_string s)
               | ident.Z_sub_get_borrow => "Z.sub_get_borrow"
               | ident.Z_sub_get_borrow_concrete s => maybe_wrap_parens with_parens ("Z.sub_get_borrow " ++ bitwidth_to_string s)
               | ident.Z_sub_with_get_borrow => "Z.sub_with_get_borrow"
               | ident.Z_sub_with_get_borrow_concrete s => maybe_wrap_parens with_parens ("Z.sub_with_get_borrow " ++ bitwidth_to_string s)
               | ident.Z_zselect => "Z.zselect"
               | ident.Z_add_modulo => "Z.add_modulo"
               | ident.Z_rshi => "Z.rshi"
               | ident.Z_rshi_concrete s offset => maybe_wrap_parens with_parens ("Z.rshi " ++ bitwidth_to_string s ++ " " ++ decimal_string_of_Z offset)
               | ident.Z_cc_m => "Z.cc_m"
               | ident.Z_cc_m_concrete s => maybe_wrap_parens with_parens ("Z.cc_m " ++ bitwidth_to_string s)
               | ident.Z_neg_snd => "Z.neg_snd"
               | ident.Z_cast range => "(" ++ show false range ++ ")"
               | ident.Z_cast2 range => "(" ++ show false range ++ ")"
               | ident.fancy_add log2wordmax imm
                 => maybe_wrap_parens with_parens ("fancy.add 2^" ++ decimal_string_of_Z log2wordmax ++ " " ++ HexString.of_Z imm)
               | ident.fancy_addc log2wordmax imm
                 => maybe_wrap_parens with_parens ("fancy.addc 2^" ++ decimal_string_of_Z log2wordmax ++ " " ++ HexString.of_Z imm)
               | ident.fancy_sub log2wordmax imm
                 => maybe_wrap_parens with_parens ("fancy.sub 2^" ++ decimal_string_of_Z log2wordmax ++ " " ++ HexString.of_Z imm)
               | ident.fancy_subb log2wordmax imm
                 => maybe_wrap_parens with_parens ("fancy.subb 2^" ++ decimal_string_of_Z log2wordmax ++ " " ++ HexString.of_Z imm)
               | ident.fancy_mulll log2wordmax
                 => maybe_wrap_parens with_parens ("fancy.mulll 2^" ++ decimal_string_of_Z log2wordmax)
               | ident.fancy_mullh log2wordmax
                 => maybe_wrap_parens with_parens ("fancy.mullh 2^" ++ decimal_string_of_Z log2wordmax)
               | ident.fancy_mulhl log2wordmax
                 => maybe_wrap_parens with_parens ("fancy.mulhl 2^" ++ decimal_string_of_Z log2wordmax)
               | ident.fancy_mulhh log2wordmax
                 => maybe_wrap_parens with_parens ("fancy.mulhh 2^" ++ decimal_string_of_Z log2wordmax)
               | ident.fancy_rshi log2wordmax x
                 => maybe_wrap_parens with_parens ("fancy.rshi 2^" ++ decimal_string_of_Z log2wordmax ++ " " ++ decimal_string_of_Z x)
               | ident.fancy_selc => "fancy.selc"
               | ident.fancy_selm log2wordmax
                 => maybe_wrap_parens with_parens ("fancy.selm 2^" ++ decimal_string_of_Z log2wordmax)
               | ident.fancy_sell => "fancy.sell"
               | ident.fancy_addm => "fancy.addm"
               end.
      End ident.

      Local Notation NewLine := (String "010" "") (only parsing).

      Module expr.
        Section with_base_type.
          Context {base_type} {ident : type.type base_type -> Type}
                  {show_base_type : Show base_type}
                  {show_ident : forall t, Show (ident t)}.
          Fixpoint show_expr_lines {t} (e : @expr.expr base_type ident (fun _ => string) t) (idx : positive) (with_parens : bool) : positive * list string
            := match e with
               | expr.Ident t idc
                 => (idx, [show with_parens idc])
               | expr.Var t v
                 => (idx, [v])
               | expr.Abs s d f
                 => (idx,
                    let n := "x" ++ decimal_string_of_pos idx in
                    let '(_, show_f) := @show_expr_lines _ (f n) (Pos.succ idx) false in
                    match show_f with
                    | nil => ["(λ " ++ n ++ ", (* NOTHING‽ *))"]%string
                    | show_f::nil
                      => ["(λ " ++ n ++ ", " ++ show_f ++ ")"]%string
                    | show_f
                      => ["(λ " ++ n ++ ","]%string ++ (List.map (String " ") show_f) ++ [")"]
                    end%list)
               | expr.App s d f x
                 => let '(idx, show_f) := @show_expr_lines _ f idx false in
                   let '(idx, show_x) := @show_expr_lines _ x idx true in
                   (idx, match show_f, show_x with
                         | [show_f], [show_x] => [maybe_wrap_parens with_parens (show_f ++ " @ " ++ show_x)]
                         | _, _ => ["("] ++ show_f ++ [") @ ("] ++ show_x ++ [")"]
                         end%list)
               | expr.LetIn A B x f
                 => let n := "x" ++ decimal_string_of_pos idx in
                   let '(_, show_x) := @show_expr_lines _ x idx false in
                   let '(idx, show_f) := @show_expr_lines _ (f n) (Pos.succ idx) false in
                   let expr_let_line := "expr_let " ++ n ++ " := " in
                   (idx,
                    match show_x with
                    | nil => [expr_let_line ++ "(* NOTHING‽ *) in"]%string ++ show_f
                    | show_x::nil => [expr_let_line ++ show_x ++ " in"]%string ++ show_f
                    | show_x::rest
                      => ([expr_let_line ++ show_x]%string)
                          ++ (List.map (fun l => String.of_list (List.repeat " "%char (String.length expr_let_line)) ++ l)%string
                                       rest)
                          ++ ["in"]
                          ++ show_f
                    end%list)
               end.
          Global Instance show_expr {t} : Show (@expr.expr base_type ident (fun _ => string) t)
            := fun with_parens e => String.concat NewLine (snd (@show_expr_lines t e 1%positive with_parens)).
          Global Instance show_Expr {t} : Show (@expr.Expr base_type ident t)
            := fun with_parens e => show with_parens (e _).
        End with_base_type.
      End expr.
    End PHOAS.

    Module C.
      Module type.
        Inductive primitive := Z | Zptr.
        Inductive type := type_primitive (t : primitive) | prod (A B : type) | unit.
        Module Export Notations.
          Global Coercion type_primitive : primitive >-> type.
          Delimit Scope Ctype_scope with Ctype.

          Bind Scope Ctype_scope with type.
          Notation "()" := unit : Ctype_scope.
          Notation "A * B" := (prod A B) : Ctype_scope.
          Notation type := type.
        End Notations.
      End type.
      Import type.Notations.

      Module int.
        Inductive type := signed (lgbitwidth : nat) | unsigned (lgbitwidth : nat).

        Definition lgbitwidth_of (t : type) : nat
          := match t with
             | signed lgbitwidth => lgbitwidth
             | unsigned lgbitwidth => lgbitwidth
             end.
        Definition bitwidth_of (t : type) : Z := 2^Z.of_nat (lgbitwidth_of t).
        Definition is_signed (t : type) : bool := match t with signed _ => true | unsigned _ => false end.
        Definition is_unsigned (t : type) : bool := negb (is_signed t).
        Definition to_zrange (t : type) : zrange
          := let bw := bitwidth_of t in
             if is_signed t
             then r[-2^bw ~> 2^(bw-1) - 1]
             else r[0 ~> 2^bw - 1].
        Definition is_tighter_than (t1 t2 : type)
          := ZRange.is_tighter_than_bool (to_zrange t1) (to_zrange t2).
        Definition of_zrange_relaxed (r : zrange) : type
          := let lg2 := Z.log2_up ((upper r - lower r) + 1) in
             let lg2u := Z.log2_up (upper r + 1) in
             let lg2l := (if (lower r <? 0) then 1 + Z.log2_up (-lower r) else 0) in
             let lg2 := Z.max lg2 (Z.max lg2u lg2l) in
             let lg2lg2u := Z.log2_up lg2 in
             if (0 <=? lower r)
             then unsigned (Z.to_nat lg2lg2u)
             else signed (Z.to_nat lg2lg2u).
        Definition of_zrange (r : zrange) : option type
          := let t := of_zrange_relaxed r in
             let r' := to_zrange t in
             if (r' =? r)%zrange
             then Some t
             else None.
        Definition unsigned_counterpart_of (t : type) : type
          := unsigned (lgbitwidth_of t).

        Definition union (t1 t2 : type) : type := of_zrange_relaxed (ZRange.union (to_zrange t1) (to_zrange t2)).

        Fixpoint base_interp (t : base.type) : Set
          := match t with
             | base.type.Z => type
             | base.type.type_base _ => unit
             | base.type.prod A B => base_interp A * base_interp B
             | base.type.list A => list (base_interp A)
             end%type.

        Module option.
          Fixpoint interp (t : base.type) : Set
            := match t with
               | base.type.Z => option type
               | base.type.type_base _ => unit
               | base.type.prod A B => interp A * interp B
               | base.type.list A => option (list (interp A))
               end%type.
          Fixpoint None {t} : interp t
            := match t with
               | base.type.Z => Datatypes.None
               | base.type.type_base _ => tt
               | base.type.prod A B => (@None A, @None B)
               | base.type.list A => Datatypes.None
               end.
          Fixpoint Some {t} : base_interp t -> interp t
            := match t with
               | base.type.Z => Datatypes.Some
               | base.type.type_base _ => fun tt => tt
               | base.type.prod A B => fun '(a, b) => (@Some A a, @Some B b)
               | base.type.list A => fun ls => Datatypes.Some (List.map (@Some A) ls)
               end.
        End option.

        Module Export Notations.
          Notation _Bool := (unsigned 0).
          Notation uint8 := (unsigned 3).
          Notation int8 := (signed 3).
          Notation uint16 := (unsigned 4).
          Notation int16 := (signed 4).
          Notation uint32 := (unsigned 5).
          Notation int32 := (signed 5).
          Notation uint64 := (unsigned 6).
          Notation int64 := (signed 6).
          Notation uint128 := (unsigned 7).
          Notation int128 := (signed 7).
        End Notations.
      End int.
      Import int.Notations.

      Example of_zrange_int32 : int.of_zrange_relaxed r[-2^31 ~> 2^31-1] = int32 := eq_refl.
      Example of_zrange_int64 : int.of_zrange_relaxed r[-2^31-1 ~> 2^31-1] = int64 := eq_refl.
      Example of_zrange_int64' : int.of_zrange_relaxed r[-2^31 ~> 2^31] = int64 := eq_refl.
      Example of_zrange_uint32 : int.of_zrange_relaxed r[0 ~> 2^32-1] = uint32 := eq_refl.
      Example of_zrange_uint64 : int.of_zrange_relaxed r[0 ~> 2^32] = uint64 := eq_refl.

      Section ident.
        Import type.
        Inductive ident : type -> type -> Set :=
        | literal (v : BinInt.Z) : ident unit Z
        | List_nth (n : Datatypes.nat) : ident Zptr Z
        | Addr : ident Z Zptr
        | Dereference : ident Zptr Z
        | Z_shiftr (offset : BinInt.Z) : ident Z Z
        | Z_shiftl (offset : BinInt.Z) : ident Z Z
        | Z_land (mask : BinInt.Z) : ident Z Z
        | Z_add : ident (Z * Z) Z
        | Z_mul : ident (Z * Z) Z
        | Z_sub : ident (Z * Z) Z
        | Z_opp : ident Z Z
        | Z_mul_split (lgs:BinInt.Z) : ident (Z * Z * Zptr) Z
        | Z_add_get_carry (lgs:BinInt.Z) : ident (Z * Z * Zptr) Z
        | Z_add_with_get_carry (lgs:BinInt.Z) : ident (Z * Z * Z * Zptr) Z
        | Z_sub_get_borrow (lgs:BinInt.Z) : ident (Z * Z * Zptr) Z
        | Z_sub_with_get_borrow (lgs:BinInt.Z) : ident (Z * Z * Z * Zptr) Z
        | Z_zselect : ident (Z * Z * Z) Z
        | Z_add_modulo : ident (Z * Z * Z) Z
        | Z_static_cast (ty : int.type) : ident Z Z
        .
      End ident.

      Inductive arith_expr : type -> Set :=
      | AppIdent {s d} (idc : ident s d) (arg : arith_expr s) : arith_expr d
      | Var (t : type.primitive) (v : string) : arith_expr t
      | Pair {A B} (a : arith_expr A) (b : arith_expr B) : arith_expr (A * B)
      | TT : arith_expr type.unit.

      Inductive stmt :=
      | Assign (declare : bool) (t : type.primitive) (sz : option int.type) (name : string) (val : arith_expr t)
      | AssignZPtr (name : string) (sz : option int.type) (val : arith_expr type.Z)
      | DeclareVar (t : type.primitive) (sz : option int.type) (name : string)
      | AssignNth (name : string) (n : nat) (val : arith_expr type.Z).
      Definition expr := list stmt.

      Module Export Notations.
        Export int.Notations.
        Export type.Notations.
        Delimit Scope Cexpr_scope with Cexpr.
        Bind Scope Cexpr_scope with expr.
        Bind Scope Cexpr_scope with stmt.
        Bind Scope Cexpr_scope with arith_expr.
        Infix "@@" := AppIdent : Cexpr_scope.
        Notation "( x , y , .. , z )" := (Pair .. (Pair x%Cexpr y%Cexpr) .. z%Cexpr) : Cexpr_scope.
        Notation "( )" := TT : Cexpr_scope.

        Notation "()" := TT : Cexpr_scope.
        Notation "x ;; y" := (@cons stmt x%Cexpr y%Cexpr) (at level 70, right associativity, format "'[v' x ;; '/' y ']'") : Cexpr_scope.
      End Notations.

      Module OfPHOAS.
        Fixpoint base_var_data (t : base.type) : Set
          := match t with
             | base.type.unit
               => unit
             | base.type.nat
             | base.type.bool
               => Empty_set
             | base.type.Z => string * option int.type
             | base.type.prod A B => base_var_data A * base_var_data B
             | base.type.list A => string * option int.type * nat
             end.
        Definition var_data (t : Compilers.type.type base.type) : Set
          := match t with
             | type.base t => base_var_data t
             | type.arrow s d => Empty_set
             end.

        Fixpoint arith_expr_for_base (t : base.type) : Set
          := match t with
             | base.type.Z
               => arith_expr type.Z * option int.type
             | base.type.prod A B
               => arith_expr_for_base A * arith_expr_for_base B
             | base.type.list A => list (arith_expr_for_base A)
             | base.type.type_base _ as t
               => base.interp t
             end.
        Definition arith_expr_for (t : Compilers.type.type base.type) : Set
          := match t with
             | type.base t => arith_expr_for_base t
             | type.arrow s d => Empty_set
             end.

        (** Quoting
            http://en.cppreference.com/w/c/language/conversion:

            ** Integer promotions

            Integer promotion is the implicit conversion of a value of
            any integer type with rank less or equal to rank of int or
            of a bit field of type _Bool, int, signed int, unsigned
            int, to the value of type int or unsigned int

            If int can represent the entire range of values of the
            original type (or the range of values of the original bit
            field), the value is converted to type int. Otherwise the
            value is converted to unsigned int. *)
        (** We assume a 32-bit [int] type *)
        Definition integer_promote_type (t : int.type) : int.type
          := if int.is_tighter_than t int32
             then int32
             else t.

        (** Quoting
            http://en.cppreference.com/w/c/language/conversion:

            rank above is a property of every integer type and is
            defined as follows:

            1) the ranks of all signed integer types are different and
               increase with their precision: rank of signed char <
               rank of short < rank of int < rank of long int < rank
               of long long int

            2) the ranks of all signed integer types equal the ranks
               of the corresponding unsigned integer types

            3) the rank of any standard integer type is greater than
               the rank of any extended integer type of the same size
               (that is, rank of __int64 < rank of long long int, but
               rank of long long < rank of __int128 due to the rule
               (1))

            4) rank of char equals rank of signed char and rank of
               unsigned char

            5) the rank of _Bool is less than the rank of any other
               standard integer type

            6) the rank of any enumerated type equals the rank of its
               compatible integer type

            7) ranking is transitive: if rank of T1 < rank of T2 and
               rank of T2 < rank of T3 then rank of T1 < rank of T3

            8) any aspects of relative ranking of extended integer
               types not covered above are implementation defined *)
        (** We define the rank to be the bitwidth, which satisfies
            (1), (2), (4), (5), and (7).  Points (3) and (6) do not
            apply. *)
        Definition rank (r : int.type) : BinInt.Z := int.bitwidth_of r.
        Definition IMPOSSIBLE {T} (v : T) : T. exact v. Qed.
        (** Quoting
            http://en.cppreference.com/w/c/language/conversion: *)
        Definition common_type (t1 t2 : int.type) : int.type
          (** First of all, both operands undergo integer promotions
              (see below). Then *)
          := let t1 := integer_promote_type t1 in
             let t2 := integer_promote_type t2 in
             (** - If the types after promotion are the same, that
                   type is the common type *)
             if int.type_beq t1 t2 then
               t1
             (** - Otherwise, if both operands after promotion have
                   the same signedness (both signed or both unsigned),
                   the operand with the lesser conversion rank (see
                   below) is implicitly converted to the type of the
                   operand with the greater conversion rank *)
             else if bool_beq (int.is_signed t1) (int.is_signed t2) then
               (if rank t1 >=? rank t2 then t1 else t2)
             (** - Otherwise, the signedness is different: If the
                   operand with the unsigned type has conversion rank
                   greater or equal than the rank of the type of the
                   signed operand, then the operand with the signed
                   type is implicitly converted to the unsigned type
                   *)
             else if int.is_unsigned t1 && (rank t1 >=? rank t2) then
               t1
             else if int.is_unsigned t2 && (rank t2 >=? rank t1) then
               t2
             (** - Otherwise, the signedness is different and the
                   signed operand's rank is greater than unsigned
                   operand's rank. In this case, if the signed type
                   can represent all values of the unsigned type, then
                   the operand with the unsigned type is implicitly
                   converted to the type of the signed operand. *)
             else if int.is_signed t1 && int.is_tighter_than t2 t1 then
               t1
             else if int.is_signed t2 && int.is_tighter_than t1 t2 then
               t2
             (** - Otherwise, both operands undergo implicit
                   conversion to the unsigned type counterpart of the
                   signed operand's type. *)
             (** N.B. This case ought to be impossible in our code,
                   where [rank] is the bitwidth. *)
             else if int.is_signed t1 then
               int.unsigned_counterpart_of t1
             else
               int.unsigned_counterpart_of t2.

        Definition Zcast_down_if_needed
          : option int.type -> arith_expr_for_base base.type.Z -> arith_expr_for_base base.type.Z
          := fun desired_type '(e, known_type)
             => match desired_type, known_type with
               | None, _ => (e, known_type)
               | Some desired_type, Some known_type
                 => if int.is_tighter_than known_type desired_type
                   then (e, Some known_type)
                   else (Z_static_cast desired_type @@ e, Some desired_type)
               | Some desired_type, None
                 => (Z_static_cast desired_type @@ e, Some desired_type)
               end%core%Cexpr.

        Fixpoint cast_down_if_needed {t}
          : int.option.interp t -> arith_expr_for_base t -> arith_expr_for_base t
          := match t with
             | base.type.Z => Zcast_down_if_needed
             | base.type.type_base _ => fun _ x => x
             | base.type.prod A B
               => fun '(r1, r2) '(e1, e2) => (@cast_down_if_needed A r1 e1,
                                          @cast_down_if_needed B r2 e2)
             | base.type.list A
               => fun r1 ls
                 => match r1 with
                   | Some r1 => List.map (fun '(r, e) => @cast_down_if_needed A r e)
                                        (List.combine r1 ls)
                   | None => ls
                   end
             end.

        Definition Zcast_up_if_needed
          : option int.type -> arith_expr_for_base base.type.Z -> arith_expr_for_base base.type.Z
          := fun desired_type '(e, known_type)
             => match desired_type, known_type with
               | None, _ | _, None => (e, known_type)
               | Some desired_type, Some known_type
                 => if int.is_tighter_than desired_type known_type
                   then (e, Some known_type)
                   else (Z_static_cast desired_type @@ e, Some desired_type)%core%Cexpr
               end.

        Fixpoint cast_up_if_needed {t}
          : int.option.interp t -> arith_expr_for_base t -> arith_expr_for_base t
          := match t with
             | base.type.Z => Zcast_up_if_needed
             | base.type.type_base _ => fun _ x => x
             | base.type.prod A B
               => fun '(r1, r2) '(e1, e2) => (@cast_up_if_needed A r1 e1,
                                          @cast_up_if_needed B r2 e2)
             | base.type.list A
               => fun r1 ls
                 => match r1 with
                   | Some r1 => List.map (fun '(r, e) => @cast_up_if_needed A r e)
                                        (List.combine r1 ls)
                   | None => ls
                   end
             end.

        Definition cast_bigger_up_if_needed
                   (desired_type : option int.type)
                   (args : arith_expr_for (base.type.Z * base.type.Z))
          : arith_expr_for (base.type.Z * base.type.Z)
          := match desired_type with
             | None => args
             | Some _
               => let '((e1, t1), (e2, t2)) := args in
                 match t1, t2 with
                 | None, _ | _, None => args
                 | Some t1', Some t2'
                   => if int.is_tighter_than t2' t1'
                     then (Zcast_up_if_needed desired_type (e1, t1), (e2, t2))
                     else ((e1, t1), Zcast_up_if_needed desired_type (e2, t2))
                 end
             end.

        Definition arith_bin_arith_expr_of_PHOAS_ident
                   (s:=(base.type.Z * base.type.Z)%etype)
                   (d:=base.type.Z)
                   (idc : ident (type.Z * type.Z) type.Z)
          : option int.type -> arith_expr_for s -> option (arith_expr_for d)
          := fun desired_type '((e1, t1), (e2, t2))
             => let t1 := option_map integer_promote_type t1 in
               let t2 := option_map integer_promote_type t2 in
               let '((e1, t1), (e2, t2))
                   := cast_bigger_up_if_needed desired_type ((e1, t1), (e2, t2)) in
               let ct := (t1 <- t1; t2 <- t2; Some (common_type t1 t2))%option in
               Some (Zcast_down_if_needed desired_type ((idc @@ (e1, e2))%Cexpr, ct)).

        Local Definition fakeprod (A B : Compilers.type.type base.type) : Compilers.type.type base.type
          := match A, B with
             | type.base A, type.base B => type.base (base.type.prod A B)
             | type.arrow _ _, _
             | _, type.arrow _ _
               => type.base (base.type.type_base base.type.unit)
             end.
        Definition arith_expr_for_uncurried_domain (t : Compilers.type.type base.type)
          := match t with
             | type.base t => unit
             | type.arrow s d => arith_expr_for (type.uncurried_domain fakeprod s d)
             end.

        Fixpoint arith_expr_of_PHOAS_ident
                 {t}
                 (idc : ident.ident t)
          : int.option.interp (type.final_codomain t) -> type.interpM_final (fun T => option T) arith_expr_for_base t
          := match idc in ident.ident t return int.option.interp (type.final_codomain t) -> type.interpM_final (fun T => option T) arith_expr_for_base t with
             | ident.Literal base.type.Z v
               => fun r => Some (cast_down_if_needed
                               r
                               (literal v @@ TT, Some (int.of_zrange_relaxed r[v~>v])))
             | ident.nil t
               => fun _ => Some nil
             | ident.cons t
               => fun r x xs => Some (cast_down_if_needed r (cons x xs))
             | ident.fst A B => fun r xy => Some (cast_down_if_needed r (@fst _ _ xy))
             | ident.snd A B => fun r xy => Some (cast_down_if_needed r (@snd _ _ xy))
             | ident.List_nth_default base.type.Z
               => fun r d ls n
                 => List.nth_default None (List.map (fun x => Some (cast_down_if_needed r x)) ls) n
             | ident.Z_shiftr offset
               => fun rout '(e, r)
                 => let rin := option_map integer_promote_type r in
                   Some (cast_down_if_needed rout (Z_shiftr offset @@ e, rin))
             | ident.Z_shiftl offset
               => fun rout '(e, r)
                 => let rin := option_map integer_promote_type r in
                   let '(e', rin') := cast_up_if_needed rout (e, rin) in
                   Some (cast_down_if_needed rout (Z_shiftr offset @@ e', rin'))
             | ident.Z_land mask
               => fun rout '(e, r)
                 => Some (cast_down_if_needed
                           rout
                           (Z_land mask @@ e,
                            option_map integer_promote_type r))
             | ident.Z_add => fun r x y => arith_bin_arith_expr_of_PHOAS_ident Z_add r (x, y)
             | ident.Z_mul => fun r x y => arith_bin_arith_expr_of_PHOAS_ident Z_mul r (x, y)
             | ident.Z_sub => fun r x y => arith_bin_arith_expr_of_PHOAS_ident Z_sub r (x, y)
             | ident.Z_zselect
               => fun rout '(econd, _) '(e1, r1) '(e2, r2)
                 => let r1 := option_map integer_promote_type r1 in
                   let r2 := option_map integer_promote_type r2 in
                   let '((e1, r1), (e2, r2))
                       := cast_bigger_up_if_needed rout ((e1, r1), (e2, r2)) in
                   let ct := (r1 <- r1; r2 <- r2; Some (common_type r1 r2))%option in
                   Some (cast_down_if_needed rout ((Z_zselect @@ (econd, e1, e2))%Cexpr, ct))
             | ident.pair A B
               => fun _ _ _ => None
             | ident.Z_opp
               => fun _ _ => None
             | ident.Literal _ v
               => fun _ => Some v
             | ident.Nat_succ
             | ident.Nat_pred
             | ident.Nat_max
             | ident.Nat_mul
             | ident.Nat_add
             | ident.Nat_sub
             | ident.pair_rect _ _ _
             | ident.bool_rect _
             | ident.nat_rect _
             | ident.list_rect _ _
             | ident.list_case _ _
             | ident.List_length _
             | ident.List_seq
             | ident.List_repeat _
             | ident.List_combine _ _
             | ident.List_map _ _
             | ident.List_app _
             | ident.List_rev _
             | ident.List_flat_map _ _
             | ident.List_partition _
             | ident.List_fold_right _ _
             | ident.List_update_nth _
             | ident.List_nth_default _
             | ident.Z_pow
             | ident.Z_div
             | ident.Z_modulo
             | ident.Z_eqb
             | ident.Z_leb
             | ident.Z_of_nat
             | ident.Z_mul_split
             | ident.Z_mul_split_concrete _
             | ident.Z_add_get_carry
             | ident.Z_add_get_carry_concrete _
             | ident.Z_add_with_carry
             | ident.Z_add_with_get_carry
             | ident.Z_add_with_get_carry_concrete _
             | ident.Z_sub_get_borrow
             | ident.Z_sub_get_borrow_concrete _
             | ident.Z_sub_with_get_borrow
             | ident.Z_sub_with_get_borrow_concrete _
             | ident.Z_add_modulo
             | ident.Z_rshi
             | ident.Z_rshi_concrete _ _
             | ident.Z_cc_m
             | ident.Z_cc_m_concrete _
             | ident.Z_neg_snd
             | ident.Z_cast _
             | ident.Z_cast2 _
             | ident.fancy_add _ _
             | ident.fancy_addc _ _
             | ident.fancy_sub _ _
             | ident.fancy_subb _ _
             | ident.fancy_mulll _
             | ident.fancy_mullh _
             | ident.fancy_mulhl _
             | ident.fancy_mulhh _
             | ident.fancy_rshi _ _
             | ident.fancy_selc
             | ident.fancy_selm _
             | ident.fancy_sell
             | ident.fancy_addm
               => fun _ => type.interpM_return _ _ _ None
             end%core%Cexpr%option%zrange.

        Fixpoint collect_args_and_apply_unknown_casts {t}
          : (int.option.interp (type.final_codomain t) -> type.interpM_final (fun T => option T) arith_expr_for_base t)
            -> type.interpM_final
                (fun T => option T)
                (fun t => int.option.interp t -> option (arith_expr_for_base t))
                t
          := match t
                   return ((int.option.interp (type.final_codomain t) -> type.interpM_final (fun T => option T) arith_expr_for_base t)
                           -> type.interpM_final
                               (fun T => option T)
                               (fun t => int.option.interp t -> option (arith_expr_for_base t))
                               t)
             with
             | type.base t => fun v => Some v
             | type.arrow (type.base s) d
               => fun f
                   (x : (int.option.interp s -> option (arith_expr_for_base s)))
                 => match x int.option.None with
                   | Some x'
                     => @collect_args_and_apply_unknown_casts
                         d
                         (fun rout => f rout x')
                   | None => type.interpM_return _ _ _ None
                   end
             | type.arrow (type.arrow _ _) _
               => fun _ => type.interpM_return _ _ _ None
             end.

        Definition collect_args_and_apply_known_casts {t}
                   (idc : ident.ident t)
          : option (type.interpM_final
                      (fun T => option T)
                      (fun t => int.option.interp t -> option (arith_expr_for_base t))
                      t)
          := match idc in ident.ident t
                   return option
                            (type.interpM_final
                               (fun T => option T)
                               (fun t => int.option.interp t -> option (arith_expr_for_base t))
                               t)
             with
             | ident.Z_cast r
               => Some (fun arg => Some (fun r' => option_map (Zcast_down_if_needed r') (arg (Some (int.of_zrange_relaxed r)))))
             | ident.Z_cast2 (r1, r2)
               => Some (fun arg => Some (fun r' => option_map (cast_down_if_needed (t:=base.type.Z*base.type.Z) r')
                                                       (arg (Some (int.of_zrange_relaxed r1), Some (int.of_zrange_relaxed r2)))))
             | ident.pair A B
               => Some (fun ea eb
                       => Some
                           (fun '(ra, rb)
                            => (ea' <- ea ra;
                                 eb' <- eb rb;
                                 Some (ea', eb'))))
             | ident.nil _
               => Some (Some (fun _ => Some nil))
             | ident.cons t
               => Some
                   (fun x xs
                    => Some
                        (fun rls
                         => let mkcons (r : int.option.interp t)
                                      (rs : int.option.interp (base.type.list t))
                               := (x <- x r;
                                     xs <- xs rs;
                                     Some (cons x xs)) in
                           match rls with
                           | Some (cons r rs) => mkcons r (Some rs)
                           | Some nil
                           | None
                             => mkcons int.option.None int.option.None
                           end))
             | _ => None
             end%option.

        Definition collect_args_and_apply_casts {t} (idc : ident.ident t)
                   (convert_no_cast : int.option.interp (type.final_codomain t) -> type.interpM_final (fun T => option T) arith_expr_for_base t)
          : type.interpM_final
              (fun T => option T)
              (fun t => int.option.interp t -> option (arith_expr_for_base t))
              t
          := match collect_args_and_apply_known_casts idc with
             | Some res => res
             | None => collect_args_and_apply_unknown_casts convert_no_cast
             end.

        Fixpoint arith_expr_of_base_PHOAS_Var
                 {t}
          : base_var_data t -> int.option.interp t -> option (arith_expr_for_base t)
          := match t with
             | base.type.Z
               => fun '(n, r) r' => Some (cast_down_if_needed r' (Var type.Z n, r))
             | base.type.prod A B
               => fun '(da, db) '(ra, rb)
                 => (ea <- @arith_expr_of_base_PHOAS_Var A da ra;
                      eb <- @arith_expr_of_base_PHOAS_Var B db rb;
                      Some (ea, eb))%option
             | base.type.list base.type.Z
               => fun '(n, r, len) r'
                 => Some (List.map
                           (fun i => (List_nth i @@ Var type.Zptr n, r))%core%Cexpr
                           (List.seq 0 len))
             | base.type.list _
             | base.type.type_base _
               => fun _ _ => None
             end.

        Fixpoint arith_expr_of_PHOAS
                 {t}
                 (e : @Compilers.expr.expr base.type ident.ident var_data t)
          : type.interpM_final
              (fun T => option T)
              (fun t => int.option.interp t -> option (arith_expr_for_base t))
              t
          := match e in expr.expr t
                   return type.interpM_final
                            (fun T => option T)
                            (fun t => int.option.interp t -> option (arith_expr_for_base t))
                            t
             with
             | expr.Var (type.base _) v
               => Some (arith_expr_of_base_PHOAS_Var v)
             | expr.Ident t idc
               => collect_args_and_apply_casts idc (arith_expr_of_PHOAS_ident idc)
             | expr.App (type.base s) d f x
               => let x' := @arith_expr_of_PHOAS s x in
                 match x' with
                 | Some x' => @arith_expr_of_PHOAS _ f x'
                 | None => type.interpM_return _ _ _ None
                 end
             | expr.Var (type.arrow _ _) _
             | expr.App (type.arrow _ _) _ _ _
             | expr.LetIn _ _ _ _
             | expr.Abs _ _ _
               => type.interpM_return _ _ _ None
             end.

        Definition arith_expr_of_base_PHOAS
                   {t:base.type}
                   (e : @Compilers.expr.expr base.type ident.ident var_data t)
                   (rout : int.option.interp t)
          : option (arith_expr_for_base t)
          := (e' <- arith_expr_of_PHOAS e; e' rout)%option.

        Fixpoint make_return_assignment_of_base_arith {t}
          : base_var_data t
            -> @Compilers.expr.expr base.type ident.ident var_data t
            -> option expr
          := match t return base_var_data t -> expr.expr t -> option expr with
             | base.type.Z
               => fun '(n, r) e
                 => (rhs <- arith_expr_of_base_PHOAS e r;
                      let '(e, r) := rhs in
                      Some [AssignZPtr n r e])
             | base.type.type_base _ => fun _ _ => None
             | base.type.prod A B
               => fun '(rva, rvb) e
                 => match invert_pair e with
                   | Some (ea, eb)
                     => (ea' <- @make_return_assignment_of_base_arith A rva ea;
                          eb' <- @make_return_assignment_of_base_arith B rvb eb;
                          Some (ea' ++ eb'))
                   | None => None
                   end
             | base.type.list base.type.Z
               => fun '(n, r, len) e
                 => (ls <- arith_expr_of_base_PHOAS e (Some (repeat r len));
                      List.fold_right
                        (fun a b
                         => match b with
                           | Some b => Some (a ++ b)
                           | None => Some a
                           end)
                        None
                        (List.map
                           (fun '(i, (e, _)) => [AssignNth n i e])
                           (List.combine (List.seq 0 len) ls)))
             | base.type.list _ => fun _ _ => None
             end%option%list.
        Definition make_return_assignment_of_arith {t}
          : var_data t
            -> @Compilers.expr.expr base.type ident.ident var_data t
            -> option expr
          := match t with
             | type.base t => make_return_assignment_of_base_arith
             | type.arrow s d => fun _ _ => None
             end.

        Definition make_assign_2arg_1ref
                   {t1 t2 d t3}
                   (r1 r2 : option int.type)
                   (x1 : @Compilers.expr.expr base.type ident.ident var_data t1)
                   (x2 : @Compilers.expr.expr base.type ident.ident var_data t2)
                   (idc : ident (type.Z * type.Z * type.Zptr) type.Z)
                   (count : positive)
                   (make_name : positive -> option string)
                   (v : var_data t3)
                   (e2 : var_data d -> var_data (base.type.Z * base.type.Z)%etype -> option expr)
          : option expr
          := (v <- type.try_transport base.try_make_transport_cps var_data _ d v;
                x1 <- type.try_transport base.try_make_transport_cps expr.expr _ base.type.Z x1;
                x2 <- type.try_transport base.try_make_transport_cps expr.expr _ base.type.Z x2;
                let e2 := e2 v in
                x1 <- arith_expr_of_base_PHOAS x1 None;
                  x2 <- arith_expr_of_base_PHOAS x2 None;
                  let '(x1, x1r) := x1 in
                  let '(x2, x2r) := x2 in
                  n1 <- make_name count;
                    n2 <- make_name (Pos.succ count);
                    e2 <- e2 ((n1, r1), (n2, r2));
                    Some ([DeclareVar type.Z r2 n2;
                             Assign true type.Z r1 n1 (idc @@ (x1, x2, Addr @@ Var type.Z n2))]
                            ++ e2))%option%list.

        Definition make_assign_3arg_1ref
                   {t1 t2 t3 d t4}
                   (r1 r2 : option int.type)
                   (x1 : @Compilers.expr.expr base.type ident.ident var_data t1)
                   (x2 : @Compilers.expr.expr base.type ident.ident var_data t2)
                   (x3 : @Compilers.expr.expr base.type ident.ident var_data t3)
                   (idc : ident (type.Z * type.Z * type.Z * type.Zptr) type.Z)
                   (count : positive)
                   (make_name : positive -> option string)
                   (v : var_data t4)
                   (e2 : var_data d -> var_data (base.type.Z * base.type.Z)%etype -> option expr)
          : option expr
          := (v <- type.try_transport base.try_make_transport_cps var_data _ d v;
                x1 <- type.try_transport base.try_make_transport_cps expr.expr _ base.type.Z x1;
                x2 <- type.try_transport base.try_make_transport_cps expr.expr _ base.type.Z x2;
                x3 <- type.try_transport base.try_make_transport_cps expr.expr _ base.type.Z x3;
                let e2 := e2 v in
                x1 <- arith_expr_of_base_PHOAS x1 None;
                  x2 <- arith_expr_of_base_PHOAS x2 None;
                  x3 <- arith_expr_of_base_PHOAS x3 None;
                  let '(x1, x1r) := x1 in
                  let '(x2, x2r) := x2 in
                  let '(x3, x3r) := x3 in
                  n1 <- make_name count;
                    n2 <- make_name (Pos.succ count);
                    e2 <- e2 ((n1, r1), (n2, r2));
                    Some ([DeclareVar type.Z r2 n2;
                             Assign true type.Z r1 n1 (idc @@ (x1, x2, x3, Addr @@ Var type.Z n2))]
                            ++ e2))%option%list.

        Fixpoint size_of_type (t : base.type) : positive
          := match t with
             | base.type.type_base t => 1
             | base.type.prod A B => size_of_type A + size_of_type B
             | base.type.list A => 1
             end%positive.

        Definition maybe_log2 (s : Z) : option Z
          := if 2^Z.log2 s =? s then Some (Z.log2 s) else None.

        Definition recognize_ident_2arg {t} (idc : ident.ident t)
          : option (ident (type.type_primitive type.Z * type.type_primitive type.Z * type.type_primitive type.Zptr) (type.type_primitive type.Z))
          := match idc with
             | ident.Z_mul_split_concrete s
               => option_map Z_mul_split (maybe_log2 s)
             | ident.Z_add_get_carry_concrete s
               => option_map Z_add_get_carry (maybe_log2 s)
             | ident.Z_sub_get_borrow_concrete s
               => option_map Z_sub_get_borrow (maybe_log2 s)
             | _ => None
             end.
        Definition recognize_ident_3arg {t} (idc : ident.ident t)
          : option (ident (type.type_primitive type.Z * type.type_primitive type.Z * type.type_primitive type.Z * type.type_primitive type.Zptr) (type.type_primitive type.Z))
          := match idc with
             | ident.Z_add_with_get_carry_concrete s
               => option_map Z_add_with_get_carry (maybe_log2 s)
             | ident.Z_sub_with_get_borrow_concrete s
               => option_map Z_sub_with_get_borrow (maybe_log2 s)
             | _ => None
             end.

        Definition make_uniform_assign_expr_of_PHOAS
                   {s} (e1 : @Compilers.expr.expr base.type ident.ident var_data s)
                   {d} (e2 : var_data s -> var_data d -> option expr)
                   (count : positive)
                   (make_name : positive -> option string)
                   (v : var_data d)
          : option expr
          := match s return (@Compilers.expr.expr base.type ident.ident var_data s)
                            -> (var_data s -> var_data d -> option expr)
                            -> option expr
             with
             | type.base (base.type.type_base base.type.Z)
               => fun e1 e2
                 => (e1 <- arith_expr_of_base_PHOAS e1 None;
                      let '(e1, r1) := e1 in
                      n1 <- make_name count;
                        e2 <- e2 (n1, r1) v;
                        Some ((Assign true type.Z r1 n1 e1)
                                :: e2))
             | type.base (base.type.Z * base.type.Z)%etype
               => fun e1 e2
                 => let '((r1, r2), e1)%core
                       := match invert_Z_cast2 e1 with
                          | Some ((r1, r2), e) => ((Some (int.of_zrange_relaxed r1), Some (int.of_zrange_relaxed r2)), e)
                          | None => ((None, None), e1)
                          end%core in
                   match e1 with
                   | (#idc @ x1 @ x2)
                     => idc <- recognize_ident_2arg idc;
                         make_assign_2arg_1ref
                           r1 r2
                           x1 x2 idc count make_name v
                           (fun v rv => e2 rv v)
                   | (#idc @ x1 @ x2 @ x3)
                     => idc <- recognize_ident_3arg idc;
                         make_assign_3arg_1ref
                           r1 r2
                           x1 x2 x3 idc count make_name v
                           (fun v rv => e2 rv v)
                   | _ => None
                   end%expr_pat
             | _ => fun _ _ => None
             end%option e1 e2.
        Definition make_assign_expr_of_PHOAS
                   {s} (e1 : @Compilers.expr.expr base.type ident.ident var_data s)
                   {s' d} (e2 : var_data s' -> var_data d -> option expr)
                   (count : positive)
                   (make_name : positive -> option string)
                   (v : var_data d)
          : option expr
          := (e1 <- type.try_transport base.try_make_transport_cps _ _ _ e1;
                make_uniform_assign_expr_of_PHOAS e1 e2 count make_name v).

        Fixpoint expr_of_base_PHOAS
                 {t}
                 (e : @Compilers.expr.expr base.type ident.ident var_data t)
                 (count : positive)
                 (make_name : positive -> option string)
                 {struct e}
          : forall (ret_val : var_data t), option expr
          := match e in expr.expr t return var_data t -> option expr with
             | expr.LetIn (type.base s) d e1 e2
               => make_assign_expr_of_PHOAS
                   e1
                   (fun vs vd => @expr_of_base_PHOAS d (e2 vs) (size_of_type s + count)%positive make_name vd)
                   count make_name
             | expr.LetIn (type.arrow _ _) _ _ _ as e
             | expr.Var _ _ as e
             | expr.Ident _ _ as e
             | expr.App _ _ _ _ as e
             | expr.Abs _ _ _ as e
               => fun v => make_return_assignment_of_arith v e
             end%expr_pat%option.

        Fixpoint base_var_data_of_bounds {t}
                 (count : positive)
                 (make_name : positive -> option string)
                 {struct t}
          : ZRange.type.base.option.interp t -> option (positive * var_data t)
          := match t return ZRange.type.base.option.interp t -> option (positive * var_data t) with
             | base.type.Z
               => fun r => (n <- make_name count;
                          Some (Pos.succ count, (n, option_map int.of_zrange_relaxed r)))
             | base.type.prod A B
               => fun '(ra, rb)
                 => (va <- @base_var_data_of_bounds A count make_name ra;
                      let '(count, va) := va in
                      vb <- @base_var_data_of_bounds B count make_name rb;
                        let '(count, vb) := vb in
                        Some (count, (va, vb)))
             | base.type.list base.type.Z
               => fun r
                 => (ls <- r;
                      n <- make_name count;
                      Some (Pos.succ count,
                            (n,
                             match List.map (option_map int.of_zrange_relaxed) ls with
                             | nil => None
                             | cons x xs
                               => List.fold_right
                                   (fun r1 r2 => r1 <- r1; r2 <- r2; Some (int.union r1 r2))
                                   x
                                   xs
                             end,
                             length ls)))
             | base.type.unit
               => fun _ => Some (count, tt)
             | base.type.list _
             | base.type.type_base _
               => fun _ => None
             end%option.

        Definition var_data_of_bounds {t}
                   (count : positive)
                   (make_name : positive -> option string)
          : ZRange.type.option.interp t -> option (positive * var_data t)
          := match t with
             | type.base t => base_var_data_of_bounds count make_name
             | type.arrow s d => fun _ => None
             end.

        Fixpoint expr_of_PHOAS
                 {t}
                 (e : @Compilers.expr.expr base.type ident.ident var_data t)
                 (make_name : positive -> option string)
                 (inbounds : type.for_each_lhs_of_arrow ZRange.type.option.interp t)
                 (outbounds : ZRange.type.option.interp (type.final_codomain t))
                 (count : positive)
                 {struct t}
          : option (type.for_each_lhs_of_arrow var_data t * var_data (type.final_codomain t) * expr)
          := match t return @Compilers.expr.expr base.type ident.ident var_data t -> type.for_each_lhs_of_arrow ZRange.type.option.interp t -> ZRange.type.option.interp (type.final_codomain t) -> option (type.for_each_lhs_of_arrow var_data t * var_data (type.final_codomain t) * expr) with
             | type.base t
               => fun e tt outbounds
                 => vd <- var_data_of_bounds count make_name outbounds;
                     let '(count, vd) := vd in
                     rv <- expr_of_base_PHOAS e count make_name vd;
                       Some (tt, vd, rv)
             | type.arrow s d
               => fun e '(inbound, inbounds) outbounds
                 => vs <- var_data_of_bounds count make_name inbound;
                     let '(count, vs) := vs in
                     f <- invert_Abs e;
                       ret <- @expr_of_PHOAS d (f vs) make_name inbounds outbounds count;
                       let '(vss, vd, rv) := ret in
                       Some (vs, vss, vd, rv)
             end%option%core%expr e inbounds outbounds.

        Definition ExprOfPHOAS
                   {t}
                   (e : @Compilers.expr.Expr base.type ident.ident t)
                   (name_list : option (list string))
                   (inbounds : type.for_each_lhs_of_arrow ZRange.type.option.interp t)
          : option (type.for_each_lhs_of_arrow var_data t * var_data (type.final_codomain t) * expr)
          := (let outbounds := partial.Extract e inbounds in
              let make_name := match name_list with
                               | None => fun p => Some ("x" ++ decimal_string_of_Z (Zpos p))
                               | Some ls => fun p => List.nth_error ls (pred (Pos.to_nat p))
                               end in
              expr_of_PHOAS (e _) make_name inbounds outbounds 1).
      End OfPHOAS.

      Module primitive.
        Definition small_enough (v : Z) : bool
          := Z.log2_up (Z.abs v + 1) <=? 128.
        Definition to_UL_postfix (r : zrange) : string
          := let lower := lower r in
             let upper := upper r in
             let u := (if lower >=? 0 then "U" else "") in
             let sz := Z.log2_up (Z.max (Z.abs upper + 1) (Z.abs lower)) in
             if sz <=? 32
             then ""
             else if sz <=? 64
                  then u ++ "L"
                  else if sz <=? 128
                       then u ++ "LL"
                       else " /* " ++ HexString.of_Z lower ++ " <= val <= " ++ HexString.of_Z upper ++ " */".

        Definition to_string {t : type.primitive} (v : BinInt.Z) : string
          := match t with
             | type.Z => HexString.of_Z v ++ (if small_enough v
                                             then to_UL_postfix r[v~>v]
                                             else "ℤ")
             | type.Zptr => "#error ""literal address " ++ HexString.of_Z v ++ """;"
             end.
      End primitive.

      Module String.
        Definition typedef_header : list string
          := ["typedef unsigned char uint1_t;"]%string.
        Module int.
          Module type.
            Definition to_string (t : int.type) : string
              := ((if int.is_unsigned t then "u" else "")
                    ++ "int"
                    ++ decimal_string_of_Z (int.bitwidth_of t)
                    ++ "_t")%string.
          End type.
        End int.

        Module type.
          Module primitive.
            Definition to_string (t : type.primitive) (r : option int.type) : string
              := match r with
                 | Some int_t => int.type.to_string int_t
                 | None => "ℤ"
                 end ++ match t with
                        | type.Zptr => "*"
                        | type.Z => ""
                        end.
          End primitive.
        End type.
      End String.

      Fixpoint arith_to_string {t} (e : arith_expr t) : string
        := match e with
           | (literal v @@ _) => primitive.to_string (t:=type.Z) v
           | (List_nth n @@ Var _ v)
             => "(" ++ v ++ "[" ++ decimal_string_of_Z (Z.of_nat n) ++ "])"
           | (Addr @@ Var _ v) => "&" ++ v
           | (Dereference @@ e) => "( *" ++ @arith_to_string _ e ++ " )"
           | (Z_shiftr offset @@ e)
             => "(" ++ @arith_to_string _ e ++ " >> " ++ decimal_string_of_Z offset ++ ")"
           | (Z_shiftl offset @@ e)
             => "(" ++ @arith_to_string _ e ++ " << " ++ decimal_string_of_Z offset ++ ")"
           | (Z_land mask @@ e)
             => "(" ++ @arith_to_string _ e ++ " & " ++ primitive.to_string (t:=type.Z) mask ++ ")"
           | (Z_add @@ (x1, x2))
             => "(" ++ @arith_to_string _ x1 ++ " + " ++ @arith_to_string _ x2 ++ ")"
           | (Z_mul @@ (x1, x2))
             => "(" ++ @arith_to_string _ x1 ++ " * " ++ @arith_to_string _ x2 ++ ")"
           | (Z_sub @@ (x1, x2))
             => "(" ++ @arith_to_string _ x1 ++ " - " ++ @arith_to_string _ x2 ++ ")"
           | (Z_opp @@ e)
             => "(-" ++ @arith_to_string _ e ++ ")"
           | (Z_mul_split lg2s @@ (x1, x2, x3))
             => "_mulx_u"
                  ++ decimal_string_of_Z lg2s ++ "("
                  ++ @arith_to_string _ x1 ++ ", "
                  ++ @arith_to_string _ x2 ++ ", "
                  ++ @arith_to_string _ x3 ++ ")"
           | (Z_add_get_carry lg2s @@ (x1, x2, x3))
             => "_add_carryx_u"
                  ++ decimal_string_of_Z lg2s ++ "(0, "
                  ++ @arith_to_string _ x1 ++ ", "
                  ++ @arith_to_string _ x2 ++ ", "
                  ++ @arith_to_string _ x3 ++ ")"
           | (Z_add_with_get_carry lg2s @@ (x1, x2, x3, x4))
             => "_add_carryx_u"
                  ++ decimal_string_of_Z lg2s ++ "("
                  ++ @arith_to_string _ x1 ++ ", "
                  ++ @arith_to_string _ x2 ++ ", "
                  ++ @arith_to_string _ x3 ++ ", "
                  ++ @arith_to_string _ x4 ++ ")"
           | (Z_sub_get_borrow lg2s @@ (x1, x2, x3))
             => "_subborrow_u"
                  ++ decimal_string_of_Z lg2s ++ "(0, "
                  ++ @arith_to_string _ x1 ++ ", "
                  ++ @arith_to_string _ x2 ++ ", "
                  ++ @arith_to_string _ x3 ++ ")"
           | (Z_sub_with_get_borrow lg2s @@ (x1, x2, x3, x4))
             => "_subborrow_u"
                  ++ decimal_string_of_Z lg2s ++ "("
                  ++ @arith_to_string _ x1 ++ ", "
                  ++ @arith_to_string _ x2 ++ ", "
                  ++ @arith_to_string _ x3 ++ ", "
                  ++ @arith_to_string _ x4 ++ ")"
           | (Z_zselect @@ (cond, et, ef)) => "#error zselect;"
           | (Z_add_modulo @@ (x1, x2, x3)) => "#error addmodulo;"
           | (Z_static_cast int_t @@ e)
             => "(" ++ String.type.primitive.to_string type.Z (Some int_t) ++ ")"
                    ++ @arith_to_string _ e
           | Var _ v => v
           | (List_nth _ @@ _)
           | (Addr @@ _)
           | (Z_add @@ _)
           | (Z_mul @@ _)
           | (Z_sub @@ _)
           | (Z_mul_split _ @@ _)
           | (Z_add_get_carry _ @@ _)
           | (Z_add_with_get_carry _ @@ _)
           | (Z_sub_get_borrow _ @@ _)
           | (Z_sub_with_get_borrow _ @@ _)
           | (Z_zselect @@ _)
           | (Z_add_modulo @@ _)
             => "#error bad_arg;"
           | Pair A B a b
             => "#error pair;"
           | TT
             => "#error tt;"
           end%core%Cexpr.

      Fixpoint stmt_to_string (e : stmt) : string
        := match e with
           | Assign true t sz name val
             => String.type.primitive.to_string t sz ++ " " ++ name ++ " = " ++ arith_to_string val ++ ";"
           | Assign false _ sz name val
             => name ++ " = " ++ arith_to_string val ++ ";"
           | AssignZPtr name sz val
             => "*" ++ name ++ " = " ++ arith_to_string val ++ ";"
           | DeclareVar t sz name
             => String.type.primitive.to_string t sz ++ " " ++ name ++ ";"
           | AssignNth name n val
             => name ++ "[" ++ decimal_string_of_Z (Z.of_nat n) ++ "] = " ++ arith_to_string val ++ ";"
           end.
      Definition to_strings (e : expr) : list string
        := List.map stmt_to_string e.

      Import OfPHOAS.

      Fixpoint to_base_arg_list {t} : base_var_data t -> list string
        := match t return base_var_data t -> _ with
           | base.type.Z
             => fun '(n, r) => [String.type.primitive.to_string type.Z r ++ " " ++ n]
           | base.type.prod A B
             => fun '(va, vb) => (@to_base_arg_list A va ++ @to_base_arg_list B vb)%list
           | base.type.list base.type.Z
             => fun '(n, r, len) => [String.type.primitive.to_string type.Z r ++ " " ++ n ++ "[" ++ decimal_string_of_Z (Z.of_nat len) ++ "]"]
           | base.type.list _ => fun _ => ["#error ""complex list"";"]
           | base.type.unit => fun _ => ["#error unit;"]
           | base.type.nat => fun _ => ["#error ℕ;"]
           | base.type.bool => fun _ => ["#error bool;"]
           end.

      Definition to_arg_list {t} : var_data t -> list string
        := match t return var_data t -> _ with
           | type.base t => to_base_arg_list
           | type.arrow _ _ => fun _ => ["#error arrow;"]
           end.

      Fixpoint to_arg_list_for_each_lhs_of_arrow {t} : type.for_each_lhs_of_arrow var_data t -> list string
        := match t return type.for_each_lhs_of_arrow var_data t -> _ with
           | type.base t => fun _ => nil
           | type.arrow s d
             => fun '(x, xs)
                => to_arg_list x ++ @to_arg_list_for_each_lhs_of_arrow d xs
           end%list.

      Fixpoint to_base_retarg_list {t} : base_var_data t -> list string
        := match t return base_var_data t -> _ with
           | base.type.Z
             => fun '(n, r) => [String.type.primitive.to_string type.Zptr r ++ " " ++ n]
           | base.type.prod A B
             => fun '(va, vb) => (@to_base_retarg_list A va ++ @to_base_retarg_list B vb)%list
           | base.type.list base.type.Z
             => fun '(n, r, len) => [String.type.primitive.to_string type.Z r ++ " " ++ n ++ "[" ++ decimal_string_of_Z (Z.of_nat len) ++ "]"]
           | base.type.list _ => fun _ => ["#error ""complex list"";"]
           | base.type.unit => fun _ => ["#error unit;"]
           | base.type.nat => fun _ => ["#error ℕ;"]
           | base.type.bool => fun _ => ["#error bool;"]
           end.

      Definition to_retarg_list {t} : var_data t -> list string
        := match t return var_data t -> _ with
           | type.base _ => to_base_arg_list
           | type.arrow _ _ => fun _ => ["#error arrow;"]
           end.

      Definition to_function_lines (name : string)
                 {t}
                 (f : type.for_each_lhs_of_arrow var_data t * var_data (type.final_codomain t) * expr)
        : list string
        := let '(args, rets, body) := f in
           (((("void "
                 ++ name ++ "("
                 ++ (String.concat ", " (to_arg_list_for_each_lhs_of_arrow args ++ to_retarg_list rets))
                 ++ ") {")%string)
               :: (List.map (fun s => "  " ++ s)%string (to_strings body)))
              ++ ["}"])%list.

      Local Notation NewLine := (String "010" "") (only parsing).

      Definition ToFunctionLines (name : string)
                 {t}
                 (e : @Compilers.expr.Expr base.type ident.ident t)
                 (name_list : option (list string))
                 (inbounds : type.for_each_lhs_of_arrow ZRange.type.option.interp t)
        : option (list string)
        := (f <- ExprOfPHOAS e name_list inbounds;
              Some (to_function_lines name f)).

      Definition LinesToString (lines : list string)
        : string
        := String.concat NewLine lines.

      Definition ToFunctionString (name : string)
                 {t}
                 (e : @Compilers.expr.Expr base.type ident.ident t)
                 (name_list : option (list string))
                 (inbounds : type.for_each_lhs_of_arrow ZRange.type.option.interp t)
        : option string
        := (ls <- ToFunctionLines name e name_list inbounds;
              Some (LinesToString ls)).
    End C.
    Notation ToFunctionLines := C.ToFunctionLines.
    Notation ToFunctionString := C.ToFunctionString.
    Notation LinesToString := C.LinesToString.
  End ToString.
End Compilers.