Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
Compiler_Dataflow.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
51#ifndef COMPILER_DATAFLOW_H
52#define COMPILER_DATAFLOW_H
53
54#include <cerrno>
55#include <climits>
56#include <cstdlib>
57#include <sstream>
58#include <string>
59#include <utility>
60
61#include <Compiler_IR_Model.H>
62#include <ah-errors.H>
63#include <tpl_dynArray.H>
64
65namespace Aleph
66{
74 {
76
84 void resize(const size_t count, const bool value = false)
85 {
86 bits.clear();
87 for (size_t i = 0; i < count; ++i)
88 bits.append(value ? 1u : 0u);
89 }
90
97 {
98 return bits.size();
99 }
100
108 bool test(const size_t index) const noexcept
109 {
110 return index < bits.size() and bits.access(index) != 0;
111 }
112
121 bool set(const size_t index, const bool value = true)
122 {
124 << "Compiler_Dataflow_Bit_Set::set(): index out of bounds";
125
126 const auto encoded = static_cast<unsigned char>(value ? 1u : 0u);
127 if (bits.access(index) == encoded)
128 return false;
129 bits.access(index) = encoded;
130 return true;
131 }
132 };
133
136 {
137 Unknown,
138 Undefined,
139 Integer,
140 Boolean,
141 Unit
142 };
143
145 inline const char *
147 {
148 switch (kind)
149 {
151 return "Unknown";
153 return "Undefined";
155 return "Integer";
157 return "Boolean";
159 return "Unit";
160 }
161
162 return "Unknown";
163 }
164
181
190
205
213
222
223 namespace Compiler_Dataflow_Detail
224 {
227 {
228 return {};
229 }
230
239
241 integer_constant(const long long value)
242 {
245 constant.integer_value = value;
246 constant.text = std::to_string(value);
247 return constant;
248 }
249
252 {
255 constant.bool_value = value;
256 constant.text = value ? "true" : "false";
257 return constant;
258 }
259
268
269 inline bool
271 const Compiler_Dataflow_Constant & rhs) noexcept
272 {
273 if (lhs.kind != rhs.kind)
274 return false;
275
276 switch (lhs.kind)
277 {
279 return lhs.integer_value == rhs.integer_value;
281 return lhs.bool_value == rhs.bool_value;
285 return true;
286 }
287
288 return false;
289 }
290
291 inline std::string
293 {
294 switch (constant.kind)
295 {
297 return "?";
299 return "undef";
301 return constant.text.empty() ? std::to_string(constant.integer_value)
302 : constant.text;
304 return constant.bool_value ? "true" : "false";
306 return "unit";
307 }
308
309 return "?";
310 }
311
312 inline bool
313 parse_integer_constant(const std::string & text,
314 long long & value) noexcept
315 {
316 if (text.empty())
317 return false;
318
319 char * end = nullptr;
320 errno = 0;
321 const long long result = std::strtoll(text.c_str(), &end, 10);
322 if (end == text.c_str() or *end != '\0')
323 return false;
324 if (errno == ERANGE)
325 return false;
326 value = result;
327 return true;
328 }
329
332 {
334 return unknown_constant();
335
336 if (inst.text == "unit")
337 return unit_constant();
338 if (inst.text == "true")
339 return boolean_constant(true);
340 if (inst.text == "false")
341 return boolean_constant(false);
342
343 long long value = 0;
345 return integer_constant(value);
346
347 return unknown_constant();
348 }
349
367
369 make_bit_set(const size_t count, const bool value = false)
370 {
372 set.resize(count, value);
373 return set;
374 }
375
379 {
381 for (size_t i = 0; i < count; ++i)
382 values.append(fill);
383 return values;
384 }
385
386 inline bool
388 const Compiler_Dataflow_Bit_Set & rhs) noexcept
389 {
390 if (lhs.size() != rhs.size())
391 return false;
392
393 for (size_t i = 0; i < lhs.size(); ++i)
394 if (lhs.bits.access(i) != rhs.bits.access(i))
395 return false;
396 return true;
397 }
398
399 inline bool
401 const DynArray<Compiler_Dataflow_Constant> & rhs) noexcept
402 {
403 if (lhs.size() != rhs.size())
404 return false;
405
406 for (size_t i = 0; i < lhs.size(); ++i)
407 if (not same_constant(lhs.access(i), rhs.access(i)))
408 return false;
409 return true;
410 }
411
414 const Compiler_Dataflow_Bit_Set & rhs)
415 {
416 ah_runtime_error_unless(lhs.size() == rhs.size())
417 << "Compiler_Dataflow: union requires sets with the same size";
418
419 auto result = make_bit_set(lhs.size(), false);
420 for (size_t i = 0; i < lhs.size(); ++i)
421 result.bits.access(i) = static_cast<unsigned char>(lhs.test(i) or rhs.test(i));
422 return result;
423 }
424
427 const Compiler_Dataflow_Bit_Set & rhs)
428 {
429 ah_runtime_error_unless(lhs.size() == rhs.size())
430 << "Compiler_Dataflow: intersection requires sets with the same size";
431
432 auto result = make_bit_set(lhs.size(), false);
433 for (size_t i = 0; i < lhs.size(); ++i)
434 result.bits.access(i) = static_cast<unsigned char>(lhs.test(i) and rhs.test(i));
435 return result;
436 }
437
440 const Compiler_Dataflow_Bit_Set & rhs)
441 {
442 ah_runtime_error_unless(lhs.size() == rhs.size())
443 << "Compiler_Dataflow: subtraction requires sets with the same size";
444
445 auto result = make_bit_set(lhs.size(), false);
446 for (size_t i = 0; i < lhs.size(); ++i)
447 result.bits.access(i) = static_cast<unsigned char>(lhs.test(i) and not rhs.test(i));
448 return result;
449 }
450
453 {
454 auto assigned = make_bit_set(function.local_slots.size(), false);
455 for (size_t i = 0; i < function.local_slots.size(); ++i)
456 if (function.local_slots.access(i).kind == Compiler_IR_Slot_Kind::Parameter)
457 assigned.set(i, true);
458 return assigned;
459 }
460
463 {
464 auto constants = make_constant_vector(function.local_slots.size(),
466 for (size_t i = 0; i < function.local_slots.size(); ++i)
467 if (function.local_slots.access(i).kind == Compiler_IR_Slot_Kind::Parameter)
468 constants.access(i) = unknown_constant();
469 return constants;
470 }
471
474 const Compiler_IR_Value_Id id)
475 {
476 if (id == 0 or id >= values.size())
477 return unknown_constant();
478 return values.access(id);
479 }
480
481 inline void
483 const Compiler_IR_Value_Id id)
484 {
485 if (id != 0 and id < used_values.size())
486 used_values.access(id) = 1u;
487 }
488
489 inline bool
491 const Compiler_IR_Value_Id id) noexcept
492 {
493 return id != 0 and id < used_values.size() and used_values.access(id) != 0;
494 }
495
496 inline bool
505
508 const Compiler_Dataflow_Constant & operand)
509 {
510 switch (op)
511 {
514 return integer_constant(operand.integer_value);
515 break;
516
519 {
520 if (operand.integer_value == LLONG_MIN)
521 break; // -LLONG_MIN overflows; do not fold
522 return integer_constant(-operand.integer_value);
523 }
524 break;
525
528 return boolean_constant(not operand.bool_value);
529 break;
530
531 default:
532 break;
533 }
534
535 return unknown_constant();
536 }
537
540 const Compiler_Dataflow_Constant & lhs,
541 const Compiler_Dataflow_Constant & rhs)
542 {
545 switch (op)
546 {
548 {
549 long long result = 0;
550#if defined(__has_builtin) && __has_builtin(__builtin_add_overflow)
552 return unknown_constant();
553#else
554 // Portable signed-addition overflow check via unsigned arithmetic.
555 const auto ul = static_cast<unsigned long long>(lhs.integer_value);
556 const auto ur = static_cast<unsigned long long>(rhs.integer_value);
557 result = static_cast<long long>(ul + ur);
558 if ((lhs.integer_value > 0 and rhs.integer_value > 0 and result < 0)
559 or (lhs.integer_value < 0 and rhs.integer_value < 0 and result >= 0))
560 return unknown_constant();
561#endif
562 return integer_constant(result);
563 }
565 {
566 long long result = 0;
567#if defined(__has_builtin) && __has_builtin(__builtin_sub_overflow)
569 return unknown_constant();
570#else
571 // Portable signed-subtraction overflow check via unsigned arithmetic.
572 const auto ul = static_cast<unsigned long long>(lhs.integer_value);
573 const auto ur = static_cast<unsigned long long>(rhs.integer_value);
574 result = static_cast<long long>(ul - ur);
575 if ((rhs.integer_value < 0 and lhs.integer_value > 0 and result < 0)
576 or (rhs.integer_value > 0 and lhs.integer_value < 0 and result >= 0))
577 return unknown_constant();
578#endif
579 return integer_constant(result);
580 }
582 {
583 long long result = 0;
584#if defined(__has_builtin) && __has_builtin(__builtin_mul_overflow)
586 return unknown_constant();
587#else
588 // Portable signed-multiplication overflow check.
589 if (lhs.integer_value == 0 or rhs.integer_value == 0)
590 {
591 result = 0;
592 }
593 else
594 {
595 const auto ul = static_cast<unsigned long long>(
596 lhs.integer_value < 0 ? -(unsigned long long)lhs.integer_value
597 : (unsigned long long)lhs.integer_value);
598 const auto ur = static_cast<unsigned long long>(
599 rhs.integer_value < 0 ? -(unsigned long long)rhs.integer_value
600 : (unsigned long long)rhs.integer_value);
601 const unsigned long long uresult = ul * ur;
602 const bool neg = (lhs.integer_value < 0) != (rhs.integer_value < 0);
603 if (ul != 0 and uresult / ul != ur)
604 return unknown_constant();
605 if (neg and uresult > static_cast<unsigned long long>(LLONG_MAX) + 1ULL)
606 return unknown_constant();
607 if (not neg and uresult > static_cast<unsigned long long>(LLONG_MAX))
608 return unknown_constant();
609 if (neg and uresult == static_cast<unsigned long long>(LLONG_MAX) + 1ULL)
610 result = LLONG_MIN;
611 else
612 result = neg ? -static_cast<long long>(uresult)
613 : static_cast<long long>(uresult);
614 }
615#endif
616 return integer_constant(result);
617 }
619 if (rhs.integer_value == 0)
620 return unknown_constant();
621 if (lhs.integer_value == LLONG_MIN and rhs.integer_value == -1)
622 return unknown_constant();
625 if (rhs.integer_value == 0)
626 return unknown_constant();
627 if (lhs.integer_value == LLONG_MIN and rhs.integer_value == -1)
628 return unknown_constant();
642 default:
643 break;
644 }
645
648 switch (op)
649 {
655 return boolean_constant(lhs.bool_value == rhs.bool_value);
657 return boolean_constant(lhs.bool_value != rhs.bool_value);
658 default:
659 break;
660 }
661
664 switch (op)
665 {
667 return boolean_constant(true);
669 return boolean_constant(false);
670 default:
671 break;
672 }
673
674 return unknown_constant();
675 }
676
682
683 inline Block_Use_Def
685 const Compiler_IR_Block & block)
686 {
688 info.use = make_bit_set(function.local_slots.size(), false);
689 info.def = make_bit_set(function.local_slots.size(), false);
690
691 for (size_t i = 0; i < block.instructions.size(); ++i)
692 {
693 const auto & inst = block.instructions.access(i);
695 and inst.local_slot_id != compiler_ir_invalid_id()
696 and inst.local_slot_id < function.local_slots.size()
697 and not info.def.test(inst.local_slot_id))
698 info.use.set(inst.local_slot_id, true);
699
701 and inst.local_slot_id != compiler_ir_invalid_id()
702 and inst.local_slot_id < function.local_slots.size())
703 info.def.set(inst.local_slot_id, true);
704 }
705
706 return info;
707 }
708
709 inline DynArray<bool>
711 {
713 for (size_t i = 0; i < function.blocks.size(); ++i)
714 reachable.append(false);
715
716 if (function.blocks.is_empty() or function.entry_block >= function.blocks.size())
717 return reachable;
718
720 worklist.append(function.entry_block);
721 while (not worklist.is_empty())
722 {
723 const auto current = worklist.pop();
724 if (current >= function.blocks.size() or reachable.access(current))
725 continue;
726
727 reachable.access(current) = true;
728 const auto & block = function.blocks.access(current);
729 for (size_t i = 0; i < block.terminator.successors.size(); ++i)
730 {
731 const auto succ = block.terminator.successors.access(i);
732 if (succ < function.blocks.size() and not reachable.access(succ))
733 worklist.append(succ);
734 }
735 }
736
737 return reachable;
738 }
739
747
748 inline Block_Simulation
750 const Compiler_IR_Block & block,
754 {
757 simulation.constant_out = constants_in;
758 simulation.value_constants = make_constant_vector(function.next_value_id + 1,
760 simulation.branch_constant = unknown_constant();
761
762 for (size_t i = 0; i < block.instructions.size(); ++i)
763 {
764 const auto & inst = block.instructions.access(i);
766
767 switch (inst.kind)
768 {
771 break;
772
774 if (inst.local_slot_id != compiler_ir_invalid_id()
775 and inst.local_slot_id < simulation.constant_out.size())
776 {
777 if (not simulation.assigned_out.test(inst.local_slot_id)
778 and findings != nullptr)
779 findings->append({block.id, i, inst.local_slot_id, inst.span});
780
781 result = simulation.assigned_out.test(inst.local_slot_id)
782 ? simulation.constant_out.access(inst.local_slot_id)
784 }
785 else
786 result = unknown_constant();
787 break;
788
790 result = evaluate_unary(inst.op,
791 value_constant(simulation.value_constants,
792 inst.operands.is_empty()
793 ? 0
794 : inst.operands.access(0)));
795 break;
796
798 result = evaluate_binary(inst.op,
799 value_constant(simulation.value_constants,
800 inst.operands.size() > 0
801 ? inst.operands.access(0)
802 : 0),
803 value_constant(simulation.value_constants,
804 inst.operands.size() > 1
805 ? inst.operands.access(1)
806 : 0));
807 break;
808
811 result = unknown_constant();
812 break;
813
815 if (inst.local_slot_id != compiler_ir_invalid_id()
816 and inst.local_slot_id < simulation.constant_out.size())
817 {
818 simulation.assigned_out.set(inst.local_slot_id, true);
819 simulation.constant_out.access(inst.local_slot_id) =
820 value_constant(simulation.value_constants,
821 inst.operands.is_empty()
822 ? 0
823 : inst.operands.access(0));
824 }
825 continue;
826 }
827
828 if (inst.result_id != 0 and inst.result_id < simulation.value_constants.size())
829 simulation.value_constants.access(inst.result_id) = result;
830 }
831
833 simulation.branch_constant =
834 value_constant(simulation.value_constants,
836
837 return simulation;
838 }
839
840 inline std::string
842 const Compiler_Dataflow_Bit_Set & set)
843 {
844 std::ostringstream out;
845 bool first = true;
846 for (size_t i = 0; i < set.size(); ++i)
847 if (set.test(i))
848 {
849 if (not first)
850 out << ", ";
851 out << Compiler_IR_Detail::local_slot_name(function.local_slots.access(i).id);
852 first = false;
853 }
854
855 return first ? "<none>" : out.str();
856 }
857
858 inline std::string
860 const DynArray<Compiler_Dataflow_Constant> & constants)
861 {
862 std::ostringstream out;
863 for (size_t i = 0; i < constants.size(); ++i)
864 {
865 if (i > 0)
866 out << ", ";
867 out << Compiler_IR_Detail::local_slot_name(function.local_slots.access(i).id)
868 << '=' << constant_to_string(constants.access(i));
869 }
870
871 return constants.is_empty() ? "<none>" : out.str();
872 }
873
874 inline void
876 {
877 for (size_t i = 0; i < function.blocks.size(); ++i)
878 function.blocks.access(i).predecessors.clear();
879
880 for (size_t i = 0; i < function.blocks.size(); ++i)
881 for (size_t j = 0; j < function.blocks.access(i).terminator.successors.size(); ++j)
882 {
883 const auto succ = function.blocks.access(i).terminator.successors.access(j);
884 if (succ < function.blocks.size())
885 function.blocks.access(succ).predecessors.append(i);
886 }
887 }
888
891 {
893 for (size_t i = 0; i < function.blocks.size(); ++i)
895
896 for (size_t i = 0; i < function.blocks.size(); ++i)
897 for (size_t j = 0; j < function.blocks.access(i).terminator.successors.size(); ++j)
898 {
899 const auto succ = function.blocks.access(i).terminator.successors.access(j);
900 if (succ < function.blocks.size())
901 predecessors.access(succ).append(i);
902 }
903
904 return predecessors;
905 }
906
907 inline size_t
909 {
910 size_t total = 0;
911 for (size_t i = 0; i < function.blocks.size(); ++i)
912 total += function.blocks.access(i).instructions.size();
913 return total;
914 }
915
916 inline bool
920 const Compiler_IR_Function & function) noexcept
921 {
923 and inst.local_slot_id != compiler_ir_invalid_id()
924 and inst.local_slot_id < function.local_slots.size())
925 return not live_after.test(inst.local_slot_id);
926
927 if (is_pure_instruction(inst) and inst.result_id != 0)
928 return not is_value_used(used_values, inst.result_id);
929
930 return false;
931 }
932 }
933
945 {
947 analysis.local_slot_count = function.local_slots.size();
948
950 const auto predecessors =
952 analysis.reachable_blocks = reachable;
953
955 for (size_t i = 0; i < function.blocks.size(); ++i)
957 function.blocks.access(i)));
958
959 for (size_t i = 0; i < function.blocks.size(); ++i)
960 {
961 analysis.live_in_slots.append(Compiler_Dataflow_Detail::make_bit_set(function.local_slots.size(), false));
962 analysis.live_out_slots.append(Compiler_Dataflow_Detail::make_bit_set(function.local_slots.size(), false));
963 analysis.assigned_in_slots.append(Compiler_Dataflow_Detail::make_bit_set(function.local_slots.size(), false));
964 analysis.assigned_out_slots.append(Compiler_Dataflow_Detail::make_bit_set(function.local_slots.size(), false));
965 analysis.constant_in_slots.append(Compiler_Dataflow_Detail::make_constant_vector(function.local_slots.size(),
967 analysis.constant_out_slots.append(Compiler_Dataflow_Detail::make_constant_vector(function.local_slots.size(),
969 }
970
971 bool changed = true;
972 while (changed)
973 {
974 changed = false;
975 for (size_t index = function.blocks.size(); index > 0; --index)
976 {
977 const auto block_id = index - 1;
978 if (block_id >= reachable.size() or not reachable.access(block_id))
979 continue;
980
981 auto live_out = Compiler_Dataflow_Detail::make_bit_set(function.local_slots.size(), false);
982 const auto & block = function.blocks.access(block_id);
983 for (size_t i = 0; i < block.terminator.successors.size(); ++i)
984 {
985 const auto succ = block.terminator.successors.access(i);
986 if (succ >= function.blocks.size() or not reachable.access(succ))
987 continue;
989 analysis.live_in_slots.access(succ));
990 }
991
992 const auto live_in =
995 live_out,
996 block_sets.access(block_id).def));
997
999 analysis.live_out_slots.access(block_id)))
1000 {
1001 analysis.live_out_slots.access(block_id) = live_out;
1002 changed = true;
1003 }
1005 analysis.live_in_slots.access(block_id)))
1006 {
1007 analysis.live_in_slots.access(block_id) = live_in;
1008 changed = true;
1009 }
1010 }
1011 }
1012
1014 changed = true;
1015 while (changed)
1016 {
1017 changed = false;
1018 for (size_t block_id = 0; block_id < function.blocks.size(); ++block_id)
1019 {
1020 if (block_id >= reachable.size() or not reachable.access(block_id))
1021 continue;
1022
1024 if (block_id == function.entry_block)
1026 else
1027 {
1028 bool have_predecessor = false;
1030 const auto & block_predecessors = predecessors.access(block_id);
1031 for (size_t i = 0; i < block_predecessors.size(); ++i)
1032 {
1033 const auto pred = block_predecessors.access(i);
1034 if (pred >= function.blocks.size() or not reachable.access(pred))
1035 continue;
1037 {
1038 assigned_in = analysis.assigned_out_slots.access(pred);
1039 have_predecessor = true;
1040 }
1041 else
1044 analysis.assigned_out_slots.access(pred));
1045 }
1046
1049 }
1050
1051 auto simulation =
1053 function.blocks.access(block_id),
1056 function.local_slots.size(),
1058
1060 analysis.assigned_in_slots.access(block_id)))
1061 {
1062 analysis.assigned_in_slots.access(block_id) = assigned_in;
1063 changed = true;
1064 }
1066 analysis.assigned_out_slots.access(block_id)))
1067 {
1068 analysis.assigned_out_slots.access(block_id) = simulation.assigned_out;
1069 changed = true;
1070 }
1071 }
1072 }
1073
1075 changed = true;
1076 while (changed)
1077 {
1078 changed = false;
1079 for (size_t block_id = 0; block_id < function.blocks.size(); ++block_id)
1080 {
1081 if (block_id >= reachable.size() or not reachable.access(block_id))
1082 continue;
1083
1085 if (block_id == function.entry_block)
1087 else
1088 {
1089 bool have_predecessor = false;
1091 function.local_slots.size(),
1093
1094 const auto & block_predecessors = predecessors.access(block_id);
1095 for (size_t i = 0; i < block_predecessors.size(); ++i)
1096 {
1097 const auto pred = block_predecessors.access(i);
1098 if (pred >= function.blocks.size() or not reachable.access(pred))
1099 continue;
1100
1102 {
1103 constant_in = analysis.constant_out_slots.access(pred);
1104 have_predecessor = true;
1105 continue;
1106 }
1107
1108 for (size_t slot = 0; slot < constant_in.size(); ++slot)
1109 constant_in.access(slot) =
1111 analysis.constant_out_slots.access(pred).access(slot));
1112 }
1113
1116 function.local_slots.size(),
1118 }
1119
1120 const auto simulation =
1122 function.blocks.access(block_id),
1123 analysis.assigned_in_slots.access(block_id),
1124 constant_in);
1125
1127 analysis.constant_in_slots.access(block_id)))
1128 {
1129 analysis.constant_in_slots.access(block_id) = constant_in;
1130 changed = true;
1131 }
1133 analysis.constant_out_slots.access(block_id)))
1134 {
1135 analysis.constant_out_slots.access(block_id) = simulation.constant_out;
1136 changed = true;
1137 }
1138 }
1139 }
1140
1141 for (size_t block_id = 0; block_id < function.blocks.size(); ++block_id)
1142 {
1143 if (block_id >= reachable.size() or not reachable.access(block_id))
1144 continue;
1145
1147 const auto simulation =
1149 function.blocks.access(block_id),
1150 analysis.assigned_in_slots.access(block_id),
1151 analysis.constant_in_slots.access(block_id),
1152 &findings);
1153 for (size_t i = 0; i < findings.size(); ++i)
1154 analysis.uninitialized_reads.append(findings.access(i));
1155
1156 if (simulation.branch_constant.kind == Compiler_Dataflow_Constant_Kind::Boolean)
1157 ++analysis.foldable_branch_count;
1158 }
1159
1160 return analysis;
1161 }
1162
1174 {
1176
1177 auto fail = [&report](const std::string & message)
1178 {
1179 report.valid = false;
1180 report.errors.append(message);
1181 };
1182
1183 if (analysis.reachable_blocks.size() != function.blocks.size())
1184 fail("Dataflow analysis for '" + function.name
1185 + "' has mismatched reachability size");
1186 if (analysis.live_in_slots.size() != function.blocks.size()
1187 or analysis.live_out_slots.size() != function.blocks.size()
1188 or analysis.assigned_in_slots.size() != function.blocks.size()
1189 or analysis.assigned_out_slots.size() != function.blocks.size()
1190 or analysis.constant_in_slots.size() != function.blocks.size()
1191 or analysis.constant_out_slots.size() != function.blocks.size())
1192 fail("Dataflow analysis for '" + function.name
1193 + "' has mismatched per-block arrays");
1194
1195 if (not report.valid)
1196 return report;
1197
1198 for (size_t block_id = 0; block_id < function.blocks.size(); ++block_id)
1199 {
1200 const auto check_set = [&fail, &function, block_id](const char * label,
1201 const Compiler_Dataflow_Bit_Set & set)
1202 {
1203 if (set.size() != function.local_slots.size())
1204 fail(std::string("Dataflow ") + label + " for "
1206 + " in function '" + function.name + "' has wrong slot-domain size");
1207 };
1208
1209 check_set("live-in", analysis.live_in_slots.access(block_id));
1210 check_set("live-out", analysis.live_out_slots.access(block_id));
1211 check_set("assigned-in", analysis.assigned_in_slots.access(block_id));
1212 check_set("assigned-out", analysis.assigned_out_slots.access(block_id));
1213
1214 if (analysis.constant_in_slots.access(block_id).size() != function.local_slots.size())
1215 fail("Dataflow constant-in state for "
1217 + " in function '" + function.name + "' has wrong slot-domain size");
1218 if (analysis.constant_out_slots.access(block_id).size() != function.local_slots.size())
1219 fail("Dataflow constant-out state for "
1221 + " in function '" + function.name + "' has wrong slot-domain size");
1222 }
1223
1224 if (function.entry_block < analysis.reachable_blocks.size()
1225 and not analysis.reachable_blocks.access(function.entry_block))
1226 fail("Dataflow analysis for '" + function.name
1227 + "' does not mark the entry block as reachable");
1228
1229 for (size_t i = 0; i < analysis.uninitialized_reads.size(); ++i)
1230 {
1231 const auto & finding = analysis.uninitialized_reads.access(i);
1232 if (finding.block_id >= function.blocks.size())
1233 fail("Dataflow analysis for '" + function.name
1234 + "' contains an uninitialized-read finding with an invalid block id");
1235 else if (finding.instruction_index
1236 >= function.blocks.access(finding.block_id).instructions.size())
1237 fail("Dataflow analysis for '" + function.name
1238 + "' contains an uninitialized-read finding with an invalid instruction index");
1239
1240 if (finding.slot_id >= function.local_slots.size())
1241 fail("Dataflow analysis for '" + function.name
1242 + "' contains an uninitialized-read finding with an invalid slot id");
1243 }
1244
1245 return report;
1246 }
1247
1259 inline std::string
1262 const Compiler_Type_Context * types = nullptr)
1263 {
1264 std::ostringstream out;
1265 if (function == nullptr)
1266 {
1267 out << "<null-dataflow-function>\n";
1268 return out.str();
1269 }
1270
1271 out << "Dataflow(" << function->name << ")\n";
1272 if (function->local_slots.is_empty())
1273 out << " LocalSlots: <none>\n";
1274 else
1275 {
1276 out << " LocalSlots:\n";
1277 for (size_t i = 0; i < function->local_slots.size(); ++i)
1278 {
1279 const auto & slot = function->local_slots.access(i);
1281 << " [" << compiler_ir_slot_kind_name(slot.kind) << "] "
1282 << slot.name;
1283 if (types != nullptr and slot.type_id != 0)
1284 out << ": " << types->to_string(slot.type_id);
1285 out << '\n';
1286 }
1287 }
1288
1289 out << " ReachableBlocks:";
1290 bool first = true;
1291 for (size_t i = 0; i < analysis.reachable_blocks.size(); ++i)
1292 if (analysis.reachable_blocks.access(i))
1293 {
1294 out << (first ? " " : ", ");
1296 first = false;
1297 }
1298 if (first)
1299 out << " <none>";
1300 out << '\n';
1301
1302 out << " FoldableBranches: " << analysis.foldable_branch_count << '\n';
1303 out << " UninitializedReads:";
1304 if (analysis.uninitialized_reads.is_empty())
1305 out << " <none>\n";
1306 else
1307 {
1308 out << '\n';
1309 for (size_t i = 0; i < analysis.uninitialized_reads.size(); ++i)
1310 {
1311 const auto & finding = analysis.uninitialized_reads.access(i);
1312 out << " " << Compiler_IR_Detail::block_name(finding.block_id)
1313 << "/I" << finding.instruction_index
1314 << " -> " << Compiler_IR_Detail::local_slot_name(finding.slot_id)
1315 << '\n';
1316 }
1317 }
1318
1319 for (size_t block_id = 0; block_id < function->blocks.size(); ++block_id)
1320 {
1321 const auto & block = function->blocks.access(block_id);
1322 out << " Block " << Compiler_IR_Detail::block_name(block_id)
1323 << " [" << block.label << "]\n";
1324 out << " Reachable: "
1325 << ((block_id < analysis.reachable_blocks.size()
1326 and analysis.reachable_blocks.access(block_id))
1327 ? "yes"
1328 : "no")
1329 << '\n';
1330 out << " LiveIn: "
1332 analysis.live_in_slots.access(block_id))
1333 << '\n';
1334 out << " LiveOut: "
1336 analysis.live_out_slots.access(block_id))
1337 << '\n';
1338 out << " AssignedIn: "
1340 analysis.assigned_in_slots.access(block_id))
1341 << '\n';
1342 out << " AssignedOut: "
1344 analysis.assigned_out_slots.access(block_id))
1345 << '\n';
1346 out << " ConstantsIn: "
1348 analysis.constant_in_slots.access(block_id))
1349 << '\n';
1350 out << " ConstantsOut: "
1352 analysis.constant_out_slots.access(block_id))
1353 << '\n';
1354 }
1355
1356 return out.str();
1357 }
1358
1372 {
1374
1375 const auto original_instruction_count =
1377
1379
1380 // Iterate to a fixpoint: repeat analysis+fold+DCE until a full pass
1381 // produces no new folded branches and no removed instructions/blocks.
1382 for (;;)
1383 {
1385 size_t pass_folded = 0;
1386 size_t pass_removed_instructions = 0;
1387
1388 for (size_t block_id = 0; block_id < provisional.blocks.size(); ++block_id)
1389 {
1390 provisional.blocks.access(block_id).predecessors.clear();
1391
1392 if (block_id >= analysis.reachable_blocks.size()
1393 or not analysis.reachable_blocks.access(block_id))
1394 continue;
1395
1396 const auto simulation =
1398 provisional.blocks.access(block_id),
1399 analysis.assigned_in_slots.access(block_id),
1400 analysis.constant_in_slots.access(block_id));
1401
1402 auto & block = provisional.blocks.access(block_id);
1404 for (size_t i = 0; i <= provisional.next_value_id; ++i)
1405 used_values.append(0u);
1406
1407 auto live_slots = analysis.live_out_slots.access(block_id);
1408 auto terminator = block.terminator;
1409 if (terminator.kind == Compiler_IR_Terminator_Kind::Branch
1411 and terminator.successors.size() == 2)
1412 {
1413 const auto chosen = simulation.branch_constant.bool_value
1414 ? terminator.successors.access(0)
1415 : terminator.successors.access(1);
1416 terminator.kind = Compiler_IR_Terminator_Kind::Jump;
1417 terminator.condition_value = 0;
1418 terminator.successors.clear();
1419 terminator.successors.append(chosen);
1420 ++pass_folded;
1421 ++result.folded_branches;
1422
1423 // Recompute live_slots from the post-fold successor so the DCE
1424 // pass below uses liveness that matches the new control flow.
1426 if (chosen < analysis.live_in_slots.size())
1427 live_slots = analysis.live_in_slots.access(chosen);
1428 }
1429
1430 if (terminator.kind == Compiler_IR_Terminator_Kind::Branch)
1432 terminator.condition_value);
1433 else if (terminator.kind == Compiler_IR_Terminator_Kind::Return)
1435 terminator.return_value);
1436
1438 for (size_t i = 0; i < block.instructions.size(); ++i)
1439 keep_flags.append(1u);
1440
1441 for (size_t index = block.instructions.size(); index > 0; --index)
1442 {
1443 const auto instruction_index = index - 1;
1444 const auto & inst = block.instructions.access(instruction_index);
1445
1446 bool keep = true;
1448 and inst.local_slot_id != compiler_ir_invalid_id()
1449 and inst.local_slot_id < provisional.local_slots.size()
1450 and not live_slots.test(inst.local_slot_id))
1451 keep = false;
1453 and inst.result_id != 0
1455 inst.result_id))
1456 keep = false;
1457
1458 keep_flags.access(instruction_index) = static_cast<unsigned char>(keep ? 1u : 0u);
1459 if (not keep)
1460 {
1462 continue;
1463 }
1464
1465 for (size_t operand = 0; operand < inst.operands.size(); ++operand)
1467 inst.operands.access(operand));
1468
1470 and inst.local_slot_id != compiler_ir_invalid_id()
1471 and inst.local_slot_id < provisional.local_slots.size())
1472 live_slots.set(inst.local_slot_id, false);
1474 and inst.local_slot_id != compiler_ir_invalid_id()
1475 and inst.local_slot_id < provisional.local_slots.size()
1476 and inst.result_id != 0)
1477 live_slots.set(inst.local_slot_id, true);
1478 }
1479
1481 for (size_t i = 0; i < block.instructions.size(); ++i)
1482 if (keep_flags.access(i) != 0)
1483 instructions.append(block.instructions.access(i));
1484 block.instructions = instructions;
1485 block.terminator = terminator;
1486 }
1487
1488 // Remove unreachable blocks created by this pass before the next iteration.
1489 const auto reachable_now =
1491 size_t pass_removed_blocks = 0;
1494 compacted.blocks.clear();
1496 for (size_t i = 0; i < provisional.blocks.size(); ++i)
1498 for (size_t block_id = 0; block_id < provisional.blocks.size(); ++block_id)
1499 {
1500 const bool keep_exit_block = block_id == provisional.exit_block;
1502 and (block_id >= reachable_now.size() or not reachable_now.access(block_id)))
1503 {
1505 continue;
1506 }
1507 pass_remap.access(block_id) = compacted.blocks.size();
1508 auto blk = provisional.blocks.access(block_id);
1509 blk.id = compacted.blocks.size();
1510 blk.predecessors.clear();
1511 compacted.blocks.append(std::move(blk));
1512 }
1513 for (size_t block_id = 0; block_id < compacted.blocks.size(); ++block_id)
1514 {
1515 auto & blk = compacted.blocks.access(block_id);
1517 for (size_t i = 0; i < blk.terminator.successors.size(); ++i)
1518 {
1519 const auto old_s = blk.terminator.successors.access(i);
1520 if (old_s < pass_remap.size()
1522 succs.append(pass_remap.access(old_s));
1523 }
1524 blk.terminator.successors = succs;
1525 }
1526 compacted.entry_block =
1527 provisional.entry_block < pass_remap.size()
1528 ? pass_remap.access(provisional.entry_block)
1530 compacted.exit_block =
1531 provisional.exit_block < pass_remap.size()
1532 ? pass_remap.access(provisional.exit_block)
1535 provisional = std::move(compacted);
1536
1538 break;
1539 }
1540
1541 // The fixpoint loop already compacted provisional after every pass.
1542 result.function = std::move(provisional);
1543 result.removed_blocks = function.blocks.size() - result.function.blocks.size();
1544 result.removed_instructions =
1546 return result;
1547 }
1548
1564 const Compiler_IR_Module * module = nullptr)
1565 {
1567
1568 auto fail = [&report](const std::string & message)
1569 {
1570 report.valid = false;
1571 report.errors.append(message);
1572 };
1573
1574 const auto ir_report = validate_ir_function(result.function, module);
1575 if (not ir_report.valid)
1576 {
1577 report.valid = false;
1578 for (size_t i = 0; i < ir_report.errors.size(); ++i)
1579 report.errors.append(ir_report.errors.access(i));
1580 }
1581
1584 result.function.exit_block < result.function.blocks.size()
1585 and result.function.exit_block < reachable.size()
1586 and not reachable.access(result.function.exit_block)
1587 ? "IR block " + Compiler_IR_Detail::block_name(result.function.exit_block)
1588 + " in function '" + result.function.name + "' is unreachable from entry"
1589 : std::string{};
1590
1591 for (size_t i = 0; i < ir_report.warnings.size(); ++i)
1592 if (ir_report.warnings.access(i).find("unreachable") != std::string::npos)
1593 {
1595 and ir_report.warnings.access(i) == expected_exit_unreachable_warning)
1596 continue;
1597
1598 fail("DCE result for '" + result.function.name
1599 + "' still contains unreachable blocks");
1600 }
1601 else
1602 report.warnings.append(ir_report.warnings.access(i));
1603
1604 const auto analysis = analyze_dataflow_function(result.function);
1605 if (analysis.foldable_branch_count != 0)
1606 fail("DCE result for '" + result.function.name
1607 + "' still contains foldable branches");
1608
1609 for (size_t block_id = 0; block_id < result.function.blocks.size(); ++block_id)
1610 {
1611 if (block_id >= analysis.reachable_blocks.size()
1612 or not analysis.reachable_blocks.access(block_id))
1613 continue;
1614
1615 const auto & block = result.function.blocks.access(block_id);
1616 auto live_slots = analysis.live_out_slots.access(block_id);
1618 for (size_t i = 0; i <= result.function.next_value_id; ++i)
1619 used_values.append(0u);
1620
1626 block.terminator.return_value);
1627
1628 for (size_t index = block.instructions.size(); index > 0; --index)
1629 {
1630 const auto instruction_index = index - 1;
1631 const auto & inst = block.instructions.access(instruction_index);
1632
1634 live_slots,
1636 result.function))
1637 fail("DCE result for '" + result.function.name
1638 + "' still contains removable code in "
1640 + " at instruction #" + std::to_string(instruction_index));
1641
1642 for (size_t operand = 0; operand < inst.operands.size(); ++operand)
1644 inst.operands.access(operand));
1645
1647 and inst.local_slot_id != compiler_ir_invalid_id()
1648 and inst.local_slot_id < result.function.local_slots.size())
1649 live_slots.set(inst.local_slot_id, false);
1651 and inst.local_slot_id != compiler_ir_invalid_id()
1652 and inst.local_slot_id < result.function.local_slots.size()
1653 and inst.result_id != 0)
1654 live_slots.set(inst.local_slot_id, true);
1655 }
1656 }
1657
1658 return report;
1659 }
1660}
1661
1662#endif
Reusable explicit-value IR model, validation, and deterministic dumps.
Exception handling system with formatted messages for Aleph-w.
#define ah_runtime_error_unless(C)
Throws std::runtime_error if condition does NOT hold.
Definition ah-errors.H:255
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
Context owning all compiler type nodes.
void clear() noexcept
Empties the container.
size_t size() const noexcept
Return the current dimension of array.
T & access(const size_t i) const noexcept
Fast access without checking allocation and bound_min_clock checking.
T & append()
Allocate a new entry to the end of array.
bool is_empty() const noexcept
Return true if the array is empty.
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Freq_Node * pred
Predecessor node in level-order traversal.
Compiler_Dataflow_Bit_Set initial_assigned_state(const Compiler_IR_Function &function)
DynArray< bool > compute_reachability(const Compiler_IR_Function &function)
Compiler_Dataflow_Bit_Set bit_set_union(const Compiler_Dataflow_Bit_Set &lhs, const Compiler_Dataflow_Bit_Set &rhs)
std::string format_slot_set(const Compiler_IR_Function &function, const Compiler_Dataflow_Bit_Set &set)
Compiler_Dataflow_Bit_Set make_bit_set(const size_t count, const bool value=false)
void mark_value_used(DynArray< unsigned char > &used_values, const Compiler_IR_Value_Id id)
Compiler_Dataflow_Constant integer_constant(const long long value)
size_t count_instructions(const Compiler_IR_Function &function)
Compiler_Dataflow_Constant constant_from_instruction(const Compiler_IR_Instruction &inst)
DynArray< Compiler_Dataflow_Constant > make_constant_vector(const size_t count, const Compiler_Dataflow_Constant &fill)
Compiler_Dataflow_Constant unknown_constant()
Compiler_Dataflow_Constant evaluate_binary(const Compiler_Operator_Kind op, const Compiler_Dataflow_Constant &lhs, const Compiler_Dataflow_Constant &rhs)
Compiler_Dataflow_Bit_Set bit_set_subtract(const Compiler_Dataflow_Bit_Set &lhs, const Compiler_Dataflow_Bit_Set &rhs)
void rebuild_predecessors(Compiler_IR_Function &function)
std::string format_slot_constants(const Compiler_IR_Function &function, const DynArray< Compiler_Dataflow_Constant > &constants)
Compiler_Dataflow_Constant unit_constant()
Compiler_Dataflow_Constant undefined_constant()
Compiler_Dataflow_Constant evaluate_unary(const Compiler_Operator_Kind op, const Compiler_Dataflow_Constant &operand)
Block_Use_Def compute_block_use_def(const Compiler_IR_Function &function, const Compiler_IR_Block &block)
DynArray< Compiler_Dataflow_Constant > initial_constant_state(const Compiler_IR_Function &function)
bool is_pure_instruction(const Compiler_IR_Instruction &inst) noexcept
Block_Simulation simulate_block(const Compiler_IR_Function &function, const Compiler_IR_Block &block, const Compiler_Dataflow_Bit_Set &assigned_in, const DynArray< Compiler_Dataflow_Constant > &constants_in, DynArray< Compiler_Dataflow_Uninitialized_Read > *findings=nullptr)
Compiler_Dataflow_Constant meet_constants(const Compiler_Dataflow_Constant &lhs, const Compiler_Dataflow_Constant &rhs)
DynArray< DynArray< Compiler_IR_Block_Id > > compute_predecessor_lists(const Compiler_IR_Function &function)
bool instruction_is_trivially_dead(const Compiler_IR_Instruction &inst, const Compiler_Dataflow_Bit_Set &live_after, const DynArray< unsigned char > &used_values, const Compiler_IR_Function &function) noexcept
Compiler_Dataflow_Constant boolean_constant(const bool value)
bool bit_set_equals(const Compiler_Dataflow_Bit_Set &lhs, const Compiler_Dataflow_Bit_Set &rhs) noexcept
Compiler_Dataflow_Bit_Set bit_set_intersection(const Compiler_Dataflow_Bit_Set &lhs, const Compiler_Dataflow_Bit_Set &rhs)
std::string constant_to_string(const Compiler_Dataflow_Constant &constant)
bool constant_vector_equals(const DynArray< Compiler_Dataflow_Constant > &lhs, const DynArray< Compiler_Dataflow_Constant > &rhs) noexcept
bool same_constant(const Compiler_Dataflow_Constant &lhs, const Compiler_Dataflow_Constant &rhs) noexcept
bool parse_integer_constant(const std::string &text, long long &value) noexcept
bool is_value_used(const DynArray< unsigned char > &used_values, const Compiler_IR_Value_Id id) noexcept
Compiler_Dataflow_Constant value_constant(const DynArray< Compiler_Dataflow_Constant > &values, const Compiler_IR_Value_Id id)
std::string local_slot_name(const Compiler_IR_Local_Slot_Id id)
std::string block_name(const Compiler_IR_Block_Id id)
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
Compiler_Dataflow_Constant_Kind
Constant-lattice states used by local constant propagation.
@ Unknown
A value exists but is not known to be constant.
@ Undefined
A slot has not been definitely initialized yet.
void message(const char *file, int line, const char *format,...)
Print an informational message with file and line info.
Definition ahDefs.C:95
Compiler_Dataflow_Validation_Report validate_dead_code_elimination(const Compiler_Dead_Code_Elimination_Result &result, const Compiler_IR_Module *module=nullptr)
Validates the result of dead-code elimination.
Compiler_Dataflow_Function_Analysis analyze_dataflow_function(const Compiler_IR_Function &function)
Computes reachability, liveness, definite assignment, and constant propagation for one IR function.
void fill(Itor beg, const Itor &end, const T &value)
Fill a range with a value.
Definition ahAlgo.H:707
const char * compiler_dataflow_constant_kind_name(const Compiler_Dataflow_Constant_Kind kind) noexcept
Returns a stable debug name for one constant-lattice kind.
and
Check uniqueness with explicit hash + equality functors.
size_t Compiler_IR_Value_Id
constexpr size_t compiler_ir_invalid_id() noexcept
Returns the sentinel invalid IR id.
Compiler_Dead_Code_Elimination_Result eliminate_dead_code(const Compiler_IR_Function &function)
Eliminates unreachable blocks, dead pure instructions, dead local stores, and folds constant-conditio...
Compiler_Dataflow_Validation_Report validate_dataflow_analysis(const Compiler_IR_Function &function, const Compiler_Dataflow_Function_Analysis &analysis)
Validates structural invariants of a dataflow result against its source function.
size_t Compiler_IR_Block_Id
const char * compiler_ir_slot_kind_name(const Compiler_IR_Slot_Kind kind) noexcept
Stable debug name for one slot kind.
Compiler_IR_Validation_Report validate_ir_function(const Compiler_IR_Function &function, const Compiler_IR_Module *module=nullptr)
Validates one lowered IR function structurally.
std::string compiler_dump_dataflow_analysis(const Compiler_IR_Function *function, const Compiler_Dataflow_Function_Analysis &analysis, const Compiler_Type_Context *types=nullptr)
Produces a deterministic, human-readable dump of a dataflow result.
Field< int > Integer
Definition ahField.H:136
Compiler_Operator_Kind
Stable operator kinds shared by reusable compiler layers.
size_t Compiler_IR_Local_Slot_Id
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
Small reusable bit-set for slot-domain dataflow analyses.
bool test(const size_t index) const noexcept
Returns whether bit index is set.
size_t size() const noexcept
Returns the number of tracked bits.
bool set(const size_t index, const bool value=true)
Sets or clears bit index.
void resize(const size_t count, const bool value=false)
Clears all existing bits and resizes the set to count elements.
DynArray< unsigned char > bits
One byte per tracked element.
One propagated constant value in the local-slot lattice.
Compiler_Dataflow_Constant_Kind kind
Lattice kind.
bool is_known() const noexcept
Returns whether the lattice state represents a known constant.
long long integer_value
Integer payload when kind == Integer.
bool bool_value
Boolean payload when kind == Boolean.
std::string text
Stable textual spelling for deterministic dumps.
DynArray< Compiler_Dataflow_Constant > constant_out
DynArray< Compiler_Dataflow_Constant > value_constants
Full dataflow result for one IR function or top-level body.
DynArray< Compiler_Dataflow_Bit_Set > assigned_in_slots
Local slots definitely assigned at block entry.
DynArray< Compiler_Dataflow_Bit_Set > assigned_out_slots
Local slots definitely assigned at block exit.
size_t local_slot_count
Number of tracked local slots.
DynArray< Compiler_Dataflow_Bit_Set > live_out_slots
Local slots live at block exit.
DynArray< DynArray< Compiler_Dataflow_Constant > > constant_in_slots
Per-block constant state at entry.
DynArray< bool > reachable_blocks
Reachability from entry for each block id.
DynArray< DynArray< Compiler_Dataflow_Constant > > constant_out_slots
Per-block constant state at exit.
DynArray< Compiler_Dataflow_Bit_Set > live_in_slots
Local slots live at block entry.
DynArray< Compiler_Dataflow_Uninitialized_Read > uninitialized_reads
Local reads that are not definitely assigned.
size_t foldable_branch_count
Reachable branches whose condition is statically known.
One definite-assignment finding for an uninitialized local read.
Source_Span span
Source span associated with the load.
Compiler_IR_Local_Slot_Id slot_id
Read local slot.
Compiler_IR_Block_Id block_id
Block containing the read.
size_t instruction_index
Zero-based instruction index inside the block.
Validation report for analysis and optimization passes.
bool valid
Whether the checked invariants hold.
DynArray< std::string > warnings
Non-fatal observations.
DynArray< std::string > errors
Hard invariant violations.
Result of applying dead-code elimination to one IR function.
size_t removed_instructions
Number of removed instructions.
Compiler_IR_Function function
Optimized function copy.
size_t removed_blocks
Number of removed blocks.
size_t folded_branches
Number of branches folded to jumps.
One basic block of IR instructions.
Lowered IR for one function or top-level body.
Compiler_IR_Value_Id next_value_id
Next value id to allocate while lowering.
DynArray< Compiler_IR_Block > blocks
Basic blocks in deterministic id order.
std::string name
Debug or source-level name.
Compiler_IR_Block_Id exit_block
Canonical exit block.
Compiler_IR_Block_Id entry_block
Canonical entry block.
DynArray< Compiler_IR_Slot > local_slots
Parameter/local slots in stable order.
One instruction producing an optional explicit result value.
Lowered IR module with shared global slots and functions.
Compiler_SSA_Block_Id id
Dense SSA block id.
Definition SSA.H:163
DynArray< Compiler_SSA_Instruction > instructions
Linear SSA instruction list.
Definition SSA.H:168
std::string label
Deterministic debug label.
Definition SSA.H:165
Compiler_SSA_Terminator terminator
Explicit block terminator.
Definition SSA.H:169
Compiler_SSA_Value_Id return_value
Return value, when relevant.
Definition SSA.H:156
DynArray< Compiler_SSA_Block_Id > successors
Successors in deterministic order.
Definition SSA.H:157
Compiler_IR_Terminator_Kind kind
Terminator category.
Definition SSA.H:153
Compiler_SSA_Value_Id condition_value
Branch condition, when relevant.
Definition SSA.H:155
Half-open byte range inside a source file.
Definition ah-source.H:100
Lazy and scalable dynamic array implementation.