-
Notifications
You must be signed in to change notification settings - Fork 85
/
Copy pathregalloc_validate.ml
1450 lines (1293 loc) · 51.7 KB
/
regalloc_validate.ml
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
(** This module implements a validator for register allocation. The algorithm is
based on a paper by Silvain Rideau and Xavier Leroy titled "Validating
Register Allocation and Spilling" which can be found here
[1] https://xavierleroy.org/publi/validation-regalloc.pdf
The solution is adapted to the different representation of CFG that is used
in this compiler, including exception handling. Also, the arguments for a
function call are specified as preassigned registers instead of
reconstructing the argument locations from the function type. *)
[@@@ocaml.warning "+a-4-30-40-41-42"]
(* CR-soon xclerc for xclerc: try to enable warning 4. *)
open! Int_replace_polymorphic_compare
module DLL = Flambda_backend_utils.Doubly_linked_list
include Cfg_intf.S
module Location : sig
type t
val of_reg : Reg.t -> t option
val of_reg_exn : Reg.t -> t
val of_regs_exn : Reg.t array -> t array
val to_loc_lossy : t -> Reg.location
val print : Cmm.machtype_component -> Format.formatter -> t -> unit
val equal : t -> t -> bool
module Set : Set.S with type elt = t
module Map : Map.S with type key = t
end = struct
module Stack = struct
(** This type is based on [Reg.stack_location]. The first difference is that
for [Stack (Local index)] this types additionally stores [stack_class]
because local stacks are separate for different stack slot classes.
Secondly for all stacks it stores index in words and not byte offset.
That gives the guarantee that if indices are different then the
locations do not overlap. *)
type t =
| Local of
{ index : int;
stack_class : Stack_class.t
}
| Incoming of { index : int }
| Outgoing of { index : int }
| Domainstate of { index : int }
let byte_bits = 8
let word_size =
(* CR-someday azewierzejew: The current implementation is limited to 64
bit architecture because then all values are of the same size and
alignment. With that assumption two stack locations can have
overlapping ranges of bytes only if the have exactly the same byte
offset.
On 32 bit architecture floats are still 64 bits but other values are 32
bits. Then for [DomainState], [Incoming] and [Outgoing] there can be
two locations with different offsets that overlap. An example is a slot
for an integer with offset 4 and a slot for a float with offset 0. The
ranges they refer to are respectively [4..8] and [0..8] so they overlap
but the offsets are different. *)
let word_size = 8 in
if Sys.word_size <> word_size * byte_bits
then
Regalloc_utils.fatal
"regalloc validation only supports 64 bit architecture, got word \
size %d"
Sys.word_size;
word_size
let byte_offset_to_word_index offset =
if offset mod word_size <> 0
then
Regalloc_utils.fatal
"regalloc validation expects aligned offsets, got offset %d with \
remainder %d"
offset (offset mod word_size);
offset / word_size
let word_index_to_byte_offset index = index * word_size
let of_stack_loc ~stack_class loc =
match loc with
| Reg.Local index -> Local { index; stack_class }
| Reg.Incoming offset ->
Incoming { index = byte_offset_to_word_index offset }
| Reg.Outgoing offset ->
Outgoing { index = byte_offset_to_word_index offset }
| Reg.Domainstate offset ->
Domainstate { index = byte_offset_to_word_index offset }
let to_stack_loc_lossy t =
match t with
| Local { index; _ } -> Reg.Local index
| Incoming { index } -> Reg.Incoming (word_index_to_byte_offset index)
| Outgoing { index } -> Reg.Outgoing (word_index_to_byte_offset index)
| Domainstate { index } ->
Reg.Domainstate (word_index_to_byte_offset index)
end
type t =
| Reg of int
| Stack of Stack.t
let of_reg reg =
match reg.Reg.loc with
| Reg.Unknown -> None
| Reg.Reg idx -> Some (Reg idx)
| Reg.Stack stack ->
Some
(Stack
(Stack.of_stack_loc
~stack_class:(Stack_class.of_machtype reg.Reg.typ)
stack))
let of_reg_exn reg = of_reg reg |> Option.get
let of_regs_exn loc_arr = Array.map of_reg_exn loc_arr
let to_loc_lossy t =
match t with
| Reg idx -> Reg.Reg idx
| Stack stack -> Reg.Stack (Stack.to_stack_loc_lossy stack)
let print typ ppf t =
Printreg.loc ~unknown:(fun _ -> assert false) ppf (to_loc_lossy t) typ
let compare (t1 : t) (t2 : t) : int =
(* CR-someday azewierzejew: Implement proper comparison. *)
Stdlib.compare t1 t2
let equal (t1 : t) (t2 : t) : bool = compare t1 t2 = 0
module T = struct
type nonrec t = t
let compare = compare
end
module Set = Set.Make (T)
module Map = Map.Make (T)
end
module Reg_id : sig
type t =
| Preassigned of { location : Location.t }
| Named of { stamp : int }
val compare : t -> t -> int
val of_reg : Reg.t -> t
val to_loc_lossy : t -> Reg.location
end = struct
type t =
| Preassigned of { location : Location.t }
| Named of { stamp : int }
let of_reg (reg : Reg.t) =
let loc = Location.of_reg reg in
if not (Bool.equal (Option.is_some loc) (Reg.is_preassigned reg))
then
Regalloc_utils.fatal
"Mismatch between register having location (%b) and register being a \
preassigned register (%b)"
(Option.is_some loc) (Reg.is_preassigned reg);
match loc with
| Some location -> Preassigned { location }
| None -> Named { stamp = reg.stamp }
let to_loc_lossy t =
match t with
| Preassigned { location } -> Location.to_loc_lossy location
| Named _ -> Reg.Unknown
let compare (t1 : t) (t2 : t) =
(* CR-someday azewierzejew: Implement proper comparison. *)
Stdlib.compare t1 t2
end
module Register : sig
module For_print : sig
type t
end
type t =
{ reg_id : Reg_id.t;
for_print : For_print.t
}
val create : Reg.t -> t
val to_dummy_reg : t -> Reg.t
val equal : t -> t -> bool
val print : Format.formatter -> t -> unit
val typ : t -> Cmm.machtype_component
module Set : Set.S with type elt = t
module Map : Map.S with type key = t
end = struct
module For_print = struct
type t =
{ raw_name : Reg.Raw_name.t;
stamp : int;
typ : Cmm.machtype_component
}
end
type t =
{ reg_id : Reg_id.t;
for_print : For_print.t
}
let create (reg : Reg.t) : t =
{ reg_id = Reg_id.of_reg reg;
for_print = { raw_name = reg.raw_name; stamp = reg.stamp; typ = reg.typ }
}
let to_dummy_reg (t : t) : Reg.t =
{ Reg.dummy with
raw_name = t.for_print.raw_name;
typ = t.for_print.typ;
stamp = t.for_print.stamp;
loc = Reg_id.to_loc_lossy t.reg_id
}
let typ (t : t) = t.for_print.typ
let print (ppf : Format.formatter) (t : t) : unit =
match t.reg_id with
| Preassigned { location } ->
Format.fprintf ppf "R[%a]" (Location.print t.for_print.typ) location
| Named _ -> Printreg.reg ppf (to_dummy_reg t)
let compare (t1 : t) (t2 : t) : int = Reg_id.compare t1.reg_id t2.reg_id
let equal (t1 : t) (t2 : t) : bool = compare t1 t2 = 0
module T = struct
type nonrec t = t
let compare = compare
end
module Set = Set.Make (T)
module Map = Map.Make (T)
end
module Instruction = struct
type 'a t =
{ desc : 'a;
arg : Register.t array;
res : Register.t array
}
module Kind = struct
type 'a t =
| Terminator : terminator t
| Basic : basic t
end
let to_prealloc (type a) ~(alloced : a instruction) (t : a t) : a instruction
=
{ alloced with
arg = Array.map Register.to_dummy_reg t.arg;
res = Array.map Register.to_dummy_reg t.res
}
end
module Description : sig
(** A snapshot of the [desc], [arg] and [res] fields of all instructions in
the CFG and [fun_args] of the CFG. It is used by the validator to record
information about the CFG before register allocation, instead of making a
deep copy of the (mutable) CFG. The description provides a type-safe and
quick access to instructions based on their IDs.
Currently, the validator assumes (without checking) that the register
allocator does not change the structure of the CFG and does not reorder
instructions within or between basic blocks.
The validator checks that the register allocator does not remove
instructions except Prologue (whenever it's allowed to do so), and does
not add any new instructions except Spill and Reload. The unique IDs of
instructions are sufficient to determine this, and the description does
not need to record the block an instruction belongs to. It is possible to
reconstruct some information about the CFG structure from the description.
For example, successors of a block can be reconstructed from the labels
that appear in terminator's [desc]. It also checks that all [fun_args]
were preassigned before allocation and that they haven't changed after. *)
type t
(** Will return [Some _] for the instructions that existed in the CFG before
allocation and [None] otherwise. Currently, only instructions that
register allocation can add are [Spill] and [Reload]. *)
val find_basic : t -> basic instruction -> basic Instruction.t option
(** Will return [Some _] for the terminators that existed in CFG before
allocation and [None] otherwise. Currently, only terminators that register
allocation can add is [Always]. *)
val find_terminator :
t -> terminator instruction -> terminator Instruction.t option
val create : Cfg_with_layout.t -> t option
val verify : t -> Cfg_with_layout.t -> unit
val reg_fun_args : t -> Register.t array
end = struct
type basic_info =
{ successor_id : InstructionId.t;
instr : basic Instruction.t
}
type t =
{ instructions : (InstructionId.t, basic_info) Hashtbl.t;
terminators : (InstructionId.t, terminator Instruction.t) Hashtbl.t;
first_instruction_in_block : InstructionId.t Label.Tbl.t;
reg_fun_args : Register.t array
}
let find_basic t instr =
Hashtbl.find_opt t.instructions instr.id
|> Option.map (fun info -> info.instr)
let find_terminator t instr = Hashtbl.find_opt t.terminators instr.id
let reg_fun_args t = t.reg_fun_args
let is_regalloc_specific_basic (desc : Cfg.basic) =
match desc with Op (Reload | Spill) -> true | _ -> false
let add_instr_id ~seen_ids ~context id =
if Hashtbl.mem seen_ids id
then
Regalloc_utils.fatal "Duplicate instruction no. %a while %s"
InstructionId.format id context;
Hashtbl.add seen_ids id ()
let add_basic ~seen_ids ~successor_id t instr =
let id = instr.id in
add_instr_id ~seen_ids
~context:"adding a basic instruction to the description" id;
if is_regalloc_specific_basic instr.desc
then
Regalloc_utils.fatal
"Instruction no. %a is specific to the regalloc phase while creating \
pre-allocation description"
InstructionId.format id;
Hashtbl.add t.instructions id
{ successor_id;
instr =
{ Instruction.desc = instr.desc;
arg = Array.map Register.create instr.arg;
res = Array.map Register.create instr.res
}
}
let add_terminator ~seen_ids t instr =
let id = instr.id in
add_instr_id ~seen_ids
~context:"adding a terminator instruction to the description" id;
Hashtbl.add t.terminators id
{ Instruction.desc = instr.desc;
arg = Array.map Register.create instr.arg;
res = Array.map Register.create instr.res
}
let do_create cfg =
Regalloc_invariants.precondition cfg;
if Lazy.force Regalloc_utils.validator_debug
then
(* CR-someday: We don't save the file with [fun_name] in the filename
because there is an appended stamp that is fragile and is annoying when
testing. Currently it's not a problem because we abort the build
whenever register allocation fails but if there was a fallback mode
then the interesting files would be instantly overwritten. *)
Cfg_with_layout.save_as_dot ~filename:"before.dot" cfg
"before_allocation_before_validation";
let basic_count, terminator_count =
Cfg_with_layout.fold_instructions cfg
~instruction:(fun (basic_count, terminator_count) _ ->
basic_count + 1, terminator_count)
~terminator:(fun (basic_count, terminator_count) _ ->
basic_count, terminator_count + 1)
~init:(0, 0)
in
let seen_ids = Hashtbl.create (basic_count + terminator_count) in
let reg_fun_args =
(Cfg_with_layout.cfg cfg).fun_args
|> Array.map (fun reg ->
let reg = Register.create reg in
(* Assert that [fun_args] are preassigned. *)
(match reg.reg_id with
| Preassigned _ -> ()
| Named _ ->
Regalloc_utils.fatal
"Register in function arguments that isn't preassigned: %a"
Register.print reg);
reg)
in
let t =
{ instructions = Hashtbl.create basic_count;
terminators = Hashtbl.create terminator_count;
first_instruction_in_block = Label.Tbl.create terminator_count;
reg_fun_args
}
in
Label.Tbl.iter
(fun _ (block : Cfg.basic_block) ->
add_terminator ~seen_ids t block.terminator;
let first_instruction_id =
DLL.fold_right
~f:(fun instr successor_id ->
add_basic ~seen_ids ~successor_id t instr;
instr.id)
block.body ~init:block.terminator.id
in
Label.Tbl.add t.first_instruction_in_block block.start
first_instruction_id)
(Cfg_with_layout.cfg cfg).Cfg.blocks;
t
let create cfg =
match !Flambda_backend_flags.regalloc_validate with
| false -> None
| true -> Some (do_create cfg)
let verify_reg_array ~context ~reg_arr ~loc_arr =
if Array.length reg_arr <> Array.length loc_arr
then
Regalloc_utils.fatal
"%s: register array length has changed. Before: %d. Now: %d." context
(Array.length reg_arr) (Array.length loc_arr);
Array.iter2
(fun (reg_desc : Register.t) loc_reg ->
match reg_desc.reg_id, Location.of_reg loc_reg with
| _, None ->
Regalloc_utils.fatal "%s: location is still unknown after allocation"
context
| Named { stamp = _ }, _ -> ()
| Preassigned { location = l1 }, Some l2 when Location.equal l1 l2 -> ()
| Preassigned { location = prev_loc }, Some new_loc ->
Regalloc_utils.fatal
"%s: changed preassigned register's location from %a to %a" context
(Location.print (Register.typ reg_desc))
prev_loc
(Location.print loc_reg.Reg.typ)
new_loc)
reg_arr loc_arr;
()
let verify_reg_arrays (type a) ~id (instr : a Cfg.instruction)
(old_instr : a Instruction.t) =
verify_reg_array
~context:
(Printf.sprintf "In instruction's no %s arguments"
(InstructionId.to_string id))
~reg_arr:old_instr.arg ~loc_arr:instr.arg;
verify_reg_array
~context:
(Printf.sprintf "In instruction's no %s results"
(InstructionId.to_string id))
~reg_arr:old_instr.res ~loc_arr:instr.res
let verify_basic ~seen_ids ~successor_id t instr =
let id = instr.id in
add_instr_id ~seen_ids
~context:"checking a basic instruction in the new CFG" id;
match
Hashtbl.find_opt t.instructions id, is_regalloc_specific_basic instr.desc
with
(* The instruction was present before. *)
| Some { instr = old_instr; successor_id = old_successor_id }, false ->
if not (InstructionId.equal old_successor_id successor_id)
then
Regalloc_utils.fatal
"The instruction's no. %a successor id has changed. Before \
allocation: %a. After allocation (ignoring instructions added by \
allocation): %a."
InstructionId.format id InstructionId.format old_successor_id
InstructionId.format successor_id;
(* CR-someday azewierzejew: Avoid using polymrphic compare. *)
(match instr.desc, old_instr.desc with
| Op (Name_for_debugger _), Op (Name_for_debugger _) ->
(* IRC uses `Reg.interf` to represent the adjacency lists for the
interference graph, which can lead to cycles. *)
()
| _ ->
(* CR-soon xclerc for xclerc: avoid polymorphic equality. *)
if Stdlib.compare instr.desc old_instr.desc <> 0
then
Regalloc_utils.fatal "The desc of instruction with id %a changed"
InstructionId.format id);
verify_reg_arrays ~id instr old_instr;
(* Return new successor id which is the id of the current instruction. *)
id
| None, true ->
(* Added regalloc specific instruction that wasn't before. The new
successor is the same as old one because this instruction is
ignored. *)
successor_id
| Some _, true ->
Regalloc_utils.fatal
"Register allocation changed existing instruction no. %a into a \
register allocation specific instruction"
InstructionId.format id
| None, false -> (
match instr.desc with
| Op Move ->
(* A move instruction, while no regalloc-specific, can be inserted
because of phi moves in split/rename. *)
successor_id
| _ ->
Regalloc_utils.fatal
"Register allocation added non-regalloc specific instruction no. %a"
InstructionId.format id)
let compare_terminators ~successor_ids ~id (old_instr : terminator)
(instr : terminator) =
let compare_label l1 l2 =
let s1 = Label.Tbl.find successor_ids l1 in
let s2 = Label.Tbl.find successor_ids l2 in
if not (InstructionId.equal s1 s2)
then
Regalloc_utils.fatal
"When checking equivalence of labels before and after allocation got \
different successor id's. Successor (label, instr id) before: (%a, \
%a). Successor (label, instr id) after: (%a, %a)."
Label.format l1 InstructionId.format s1 Label.format l2
InstructionId.format s2
in
match old_instr, instr with
| Never, Never -> ()
| Always l1, Always l2 -> compare_label l1 l2
| ( Parity_test { ifso = ifso1; ifnot = ifnot1 },
Parity_test { ifso = ifso2; ifnot = ifnot2 } ) ->
compare_label ifso1 ifso2;
compare_label ifnot1 ifnot2
| ( Truth_test { ifso = ifso1; ifnot = ifnot1 },
Truth_test { ifso = ifso2; ifnot = ifnot2 } ) ->
compare_label ifso1 ifso2;
compare_label ifnot1 ifnot2
| ( Float_test { width = w1; lt = lt1; eq = eq1; gt = gt1; uo = uo1 },
Float_test { width = w2; lt = lt2; eq = eq2; gt = gt2; uo = uo2 } )
when Cmm.equal_float_width w1 w2 ->
compare_label lt1 lt2;
compare_label eq1 eq2;
compare_label gt1 gt2;
compare_label uo1 uo2
| ( Int_test { lt = lt1; eq = eq1; gt = gt1; is_signed = sign1; imm = imm1 },
Int_test { lt = lt2; eq = eq2; gt = gt2; is_signed = sign2; imm = imm2 }
)
when Bool.equal sign1 sign2 && Option.equal Int.equal imm1 imm2 ->
compare_label lt1 lt2;
compare_label eq1 eq2;
compare_label gt1 gt2
| Switch labels1, Switch labels2 ->
Array.iter2 (fun l1 l2 -> compare_label l1 l2) labels1 labels2
| Return, Return -> ()
| Raise rk1, Raise rk2
(* CR-someday azewierzejew: Avoid using polymorphic comparison. *)
when Stdlib.compare rk1 rk2 = 0 ->
()
| Tailcall_self { destination = l1 }, Tailcall_self { destination = l2 } ->
compare_label l1 l2
| Tailcall_func call1, Tailcall_func call2
(* CR-someday azewierzejew: Avoid using polymorphic comparison. *)
when Stdlib.compare call1 call2 = 0 ->
()
| Call_no_return call1, Call_no_return call2
(* CR-someday azewierzejew: Avoid using polymorphic comparison. *)
when Stdlib.compare call1 call2 = 0 ->
()
| ( Call { op = call1; label_after = l1 },
Call { op = call2; label_after = l2 } )
(* CR-someday azewierzejew: Avoid using polymorphic comparison. *)
when Stdlib.compare call1 call2 = 0 ->
compare_label l1 l2
| ( Prim { op = prim1; label_after = l1 },
Prim { op = prim2; label_after = l2 } )
(* CR-someday azewierzejew: Avoid using polymorphic comparison. *)
when Stdlib.compare prim1 prim2 = 0 ->
compare_label l1 l2
| _ ->
Regalloc_utils.fatal
"The desc of terminator with id %a changed, before: %a, after: %a."
InstructionId.format id
(Cfg.dump_terminator ~sep:", ")
old_instr
(Cfg.dump_terminator ~sep:", ")
instr
let verify_terminator ~seen_ids ~(successor_ids : InstructionId.t Label.Tbl.t)
t instr : InstructionId.t =
let id = instr.id in
add_instr_id ~seen_ids
~context:"checking a terminator instruction in the new CFG" id;
match Hashtbl.find_opt t.terminators id with
(* The instruction was present before. *)
| Some old_instr ->
verify_reg_arrays ~id instr old_instr;
compare_terminators ~successor_ids ~id old_instr.desc instr.desc;
id
| None -> (
match instr.desc with
| Always successor ->
(* A terminator added by the register allocator. The successor
instruction can be found in the next block. *)
Label.Tbl.find successor_ids successor
| _ ->
Regalloc_utils.fatal
"Register allocation added a terminator no. %a but that's not \
allowed for this type of terminator: %a"
InstructionId.format id Cfg.print_terminator instr)
let compute_successor_ids t (cfg : Cfg.t) =
let visited_labels = Label.Tbl.create (Label.Tbl.length cfg.blocks) in
let successor_ids = Label.Tbl.create (Label.Tbl.length cfg.blocks) in
(* Finds and stores successor id for a given block. *)
let rec get_id (block : Cfg.basic_block) =
match Label.Tbl.find_opt successor_ids block.start with
| Some id -> id
| None ->
if Label.Tbl.mem visited_labels block.start
then
Misc.fatal_errorf
"Visiting the same block %a without knowing the successor \
instruction's id. That means there's a loop consisting of only \
instructions added by the register allocator."
Label.format block.start;
Label.Tbl.add visited_labels block.start ();
let first_id = get_first_non_regalloc_id t block in
Label.Tbl.add successor_ids block.start first_id;
first_id
(* Finds successor id in or after the given block. *)
and get_first_non_regalloc_id t (block : Cfg.basic_block) =
let res : Cfg.basic Cfg.instruction option =
DLL.fold_left
~f:(fun acc instr ->
match acc with
| Some _ -> acc
| None ->
if Hashtbl.mem t.instructions instr.id then Some instr else None)
block.body ~init:None
in
match res with
| Some instr -> instr.id
| None -> (
match
block.terminator.desc, Hashtbl.mem t.terminators block.terminator.id
with
| _, true -> block.terminator.id
| Always label, false -> get_id (Cfg.get_block_exn cfg label)
| _, false ->
Regalloc_utils.fatal
"Register allocation added a terminator no. %a but that's not \
allowed for this type of terminator: %a"
InstructionId.format block.terminator.id Cfg.print_terminator
block.terminator)
in
Label.Tbl.iter
(fun _ block ->
(* Force compuatation of the given id. *)
let (_ : InstructionId.t) = get_id block in
())
cfg.blocks;
successor_ids
let verify t cfg =
Regalloc_invariants.postcondition_layout cfg;
verify_reg_array ~reg_arr:t.reg_fun_args ~context:"In function arguments"
~loc_arr:(Cfg_with_layout.cfg cfg).fun_args;
let seen_ids =
Hashtbl.create
(Hashtbl.length t.instructions + Hashtbl.length t.terminators)
in
let successor_ids = compute_successor_ids t (Cfg_with_layout.cfg cfg) in
Label.Tbl.iter
(fun _ (block : Cfg.basic_block) ->
let successor_id =
verify_terminator ~seen_ids ~successor_ids t block.terminator
in
let first_instruction_id =
DLL.fold_right
~f:(fun instr successor_id ->
verify_basic ~seen_ids ~successor_id t instr)
block.body ~init:successor_id
in
ignore (first_instruction_id : InstructionId.t))
(Cfg_with_layout.cfg cfg).Cfg.blocks;
Hashtbl.iter
(fun id { instr; _ } ->
let can_be_removed =
match instr.Instruction.desc with
| Prologue ->
let ({ fun_contains_calls; fun_num_stack_slots; _ } : Cfg.t) =
Cfg_with_layout.cfg cfg
in
not
(Proc.prologue_required ~fun_contains_calls ~fun_num_stack_slots)
| _ -> false
in
if (not (Hashtbl.mem seen_ids id)) && not can_be_removed
then
Regalloc_utils.fatal
"Instruction no. %a was deleted by register allocator"
InstructionId.format id)
t.instructions;
Hashtbl.iter
(fun id _ ->
if not (Hashtbl.mem seen_ids id)
then
Regalloc_utils.fatal
"Terminator no. %a was deleted by register allocator"
InstructionId.format id)
t.terminators
end
module Equation_set : sig
(** This corresponds to the set of equations defined in section 3.2 of in the
paper [1]. The definition is simplified substantially because here none of
the locations (e.g. registers or stack) are allowed to overlap. *)
type t
val empty : t
val union : t -> t -> t
val subset : t -> t -> bool
(** This corresponds to case (10) in Fig. 1 of the paper [1]. *)
val rename_loc : arg:Location.t -> res:Location.t -> t -> t
(** This corresponds to case (3) in Fig. 1 of the paper [1]. *)
val rename_reg : arg:Register.t -> res:Register.t -> t -> t
(** Calling [remove_result], [verify_destoyed_locations] and [add_argument] in
this order corresponds to case (7) in Fig. 1 of the paper [1]. This
implementation is also generalized for all cases not handled by
[rename_loc] or [rename_reg]. *)
val remove_result :
reg_res:Register.t array ->
loc_res:Location.t array ->
t ->
(t, string) Result.t
val verify_destroyed_locations :
destroyed:Location.t array -> t -> (t, string) Result.t
val add_argument :
reg_arg:Register.t array -> loc_arg:Location.t array -> t -> t
val is_empty : t -> bool
val print : Format.formatter -> t -> unit
end = struct
module Equation = struct
(** The equations here contain a bit more than the paper [1] has. In the
said paper the equation is a named variable on one side and location on
the other side because moving arguments to the correct position before
call is done implicitly.
In our representation of CFG before the call [foo %rax] there is
explicit instruction [%rax := x]. Therefore the left-hand side of the
equation in our case is either [Named] or [Preassigned]. The equations
for [Preassigned { location }] are always expected to have exactly
[location] on the right-hand side.
It's possible to have very specific code with only no-op moves, spills
and reloads that breaks the previous assumption but there are multiple
soft assumptions that prevent existence of such code. Result of
validation on such code is subject to change and shouldn't be relied
upon.
For that reason equations of form [Preassigned { location } = location]
give us implicitly a set of live preassigned locations. That verifies
that none of the preassigned locations are destroyed or assigning to the
preassigned location doesn't destroy a [Named] variable. *)
type t = Register.t * Location.t
let print ppf (r, l) =
Format.fprintf ppf "%a=%a" Register.print r
(Location.print (Register.typ r))
l
end
exception Verification_failed of string
type t =
{ for_loc : Register.Set.t Location.Map.t;
for_reg : Location.Set.t Register.Map.t
}
let empty = { for_loc = Location.Map.empty; for_reg = Register.Map.empty }
let remove ((eq_reg, eq_loc) : Equation.t) t =
{ for_loc =
Location.Map.update eq_loc
(function
| None -> None
| Some set ->
let set = Register.Set.remove eq_reg set in
if Register.Set.is_empty set then None else Some set)
t.for_loc;
for_reg =
Register.Map.update eq_reg
(function
| None -> None
| Some set ->
let set = Location.Set.remove eq_loc set in
if Location.Set.is_empty set then None else Some set)
t.for_reg
}
let add ((eq_reg, eq_loc) : Equation.t) t =
{ for_loc =
Location.Map.update eq_loc
(fun set ->
match set with
| None -> Some (Register.Set.singleton eq_reg)
| Some set -> Some (Register.Set.add eq_reg set))
t.for_loc;
for_reg =
Register.Map.update eq_reg
(function
| None -> Some (Location.Set.singleton eq_loc)
| Some set -> Some (Location.Set.add eq_loc set))
t.for_reg
}
let is_empty t =
let loc_res = Location.Map.is_empty t.for_loc in
let reg_res = Register.Map.is_empty t.for_reg in
assert (Bool.equal loc_res reg_res);
loc_res
let subset t1 t2 =
Location.Map.for_all
(fun loc regs1 ->
assert (not (Register.Set.is_empty regs1));
match Location.Map.find_opt loc t2.for_loc with
| None -> false
| Some regs2 -> Register.Set.subset regs1 regs2)
t1.for_loc
let union t1 t2 =
{ for_loc =
Location.Map.merge
(fun _loc regs1 regs2 ->
match regs1, regs2 with
| None, None -> None
| Some r, None | None, Some r -> Some r
| Some r1, Some r2 -> Some (Register.Set.union r1 r2))
t1.for_loc t2.for_loc;
for_reg =
Register.Map.merge
(fun _reg locs1 locs2 ->
match locs1, locs2 with
| None, None -> None
| Some l, None | None, Some l -> Some l
| Some l1, Some l2 -> Some (Location.Set.union l1 l2))
t1.for_reg t2.for_reg
}
let array_fold2 f acc arr1 arr2 =
let acc = ref acc in
Array.iter2 (fun v1 v2 -> acc := f !acc v1 v2) arr1 arr2;
!acc
let compatible_one ~reg ~loc t =
let check_equation eq_reg eq_loc =
(* This check corresponds to simplified check that "(x, l) is compatible
with E" from chapter 3.2 section "Unsatisfiability and Overlap" from
the paper [1], where "x" is [reg] and "l" is [loc]. Because we don't
consider overlap at all, the condition simplifies to [(x' = x && l' =
l) || (x' <> x && l' <> l)]. *)
let reg_eq = Register.equal eq_reg reg in
let loc_eq = Location.equal eq_loc loc in
if not (Bool.equal reg_eq loc_eq)
then (
Format.fprintf Format.str_formatter
"Unsatisfiable equations when removing result equations.\n\
Existing equation has to agree on 0 or 2 sides (cannot be exactly \
1) with the removed equation.\n\
Existing equation %a.\n\
Removed equation: %a." Equation.print (eq_reg, eq_loc) Equation.print
(reg, loc);
let message = Format.flush_str_formatter () in
raise (Verification_failed message))
in
(* Check equations that have the location the same. *)
(match Location.Map.find_opt loc t.for_loc with
| None -> ()
| Some regs ->
let eq_loc = loc in
Register.Set.iter (fun eq_reg -> check_equation eq_reg eq_loc) regs);
(* Check equations that have the register the same. *)
(match Register.Map.find_opt reg t.for_reg with
| None -> ()
| Some locs ->
let eq_reg = reg in
Location.Set.iter (fun eq_loc -> check_equation eq_reg eq_loc) locs);
()
let remove_result ~reg_res ~loc_res t =
try
Array.iter2 (fun reg loc -> compatible_one ~reg ~loc t) reg_res loc_res;
let t =
array_fold2 (fun t reg loc -> remove (reg, loc) t) t reg_res loc_res
in
Ok t
with Verification_failed message -> Error message
let verify_destroyed_locations ~destroyed t =
(* CR azewierzejew for azewierzejew: Add checking stack for stack_location
other than Local. *)
try
Array.iter
(fun destroyed_loc ->
match Location.Map.find_opt destroyed_loc t.for_loc with
| None -> ()
| Some regs ->
assert (not (Register.Set.is_empty regs));
let typ = Register.Set.choose regs |> Register.typ in
Format.fprintf Format.str_formatter
"Destroying a location %a in which live registers %a are stored"
(Location.print typ) destroyed_loc
(Format.pp_print_seq
~pp_sep:(fun ppf () -> Format.fprintf ppf ", ")
Register.print)
(Register.Set.to_seq regs);
let message = Format.flush_str_formatter () in
raise (Verification_failed message))
destroyed;
Ok t
with Verification_failed message -> Error message
let add_argument ~reg_arg ~loc_arg t =
array_fold2 (fun t reg loc -> add (reg, loc) t) t reg_arg loc_arg
let rename_loc ~arg ~res t =
let regs =
Location.Map.find_opt res t.for_loc
|> Option.value ~default:Register.Set.empty
in
Register.Set.fold
(fun reg acc -> acc |> remove (reg, res) |> add (reg, arg))
regs t
let rename_reg ~arg ~res t =
let locs =
Register.Map.find_opt res t.for_reg
|> Option.value ~default:Location.Set.empty
in
Location.Set.fold
(fun loc acc -> acc |> remove (res, loc) |> add (arg, loc))
locs t
let print ppf t =
let first = ref true in
let print_eq eq_reg eq_loc =
if !first then first := false else Format.fprintf ppf " ";
Format.fprintf ppf "%a" Equation.print (eq_reg, eq_loc)
in
Location.Map.iter
(fun eq_loc regs ->
Register.Set.iter (fun eq_reg -> print_eq eq_reg eq_loc) regs)
t.for_loc
end
module type Description_value = sig
val description : Description.t
end
let print_reg_as_loc ppf reg =
Printreg.loc
~unknown:(fun ppf -> Format.fprintf ppf "<Unknown>")
ppf reg.Reg.loc reg.Reg.typ
module Domain : Cfg_dataflow.Domain_S with type t = Equation_set.t = struct
(** This type corresponds to the set of equations in the dataflow from the
paper [1]. The value corresponing to "Top" is not represented. The
transfer function return [(domain, error) result] and the "Top" element
corresponds to [Error _] for which dataflow is terminated immediately
without propagating the error value all the way to the entrypoint. A
side-effect of this is that an erroreous but unreachable code will make
the validation fail (where the algorithm in [1] will allow such code). *)