51#ifndef COMPILER_DATAFLOW_H
52#define COMPILER_DATAFLOW_H
87 for (
size_t i = 0; i <
count; ++i)
108 bool test(
const size_t index)
const noexcept
121 bool set(
const size_t index,
const bool value =
true)
124 <<
"Compiler_Dataflow_Bit_Set::set(): index out of bounds";
126 const auto encoded =
static_cast<unsigned char>(
value ? 1u : 0u);
223 namespace Compiler_Dataflow_Detail
236 value.text =
"undef";
273 if (lhs.kind != rhs.kind)
279 return lhs.integer_value == rhs.integer_value;
281 return lhs.bool_value == rhs.bool_value;
304 return constant.bool_value ?
"true" :
"false";
314 long long &
value)
noexcept
319 char * end =
nullptr;
321 const long long result = std::strtoll(text.c_str(), &end, 10);
322 if (end == text.c_str()
or *end !=
'\0')
336 if (
inst.text ==
"unit")
338 if (
inst.text ==
"true")
340 if (
inst.text ==
"false")
381 for (
size_t i = 0; i <
count; ++i)
390 if (lhs.size() != rhs.size())
393 for (
size_t i = 0; i < lhs.size(); ++i)
394 if (lhs.bits.access(i) != rhs.bits.access(i))
403 if (lhs.size() != rhs.size())
406 for (
size_t i = 0; i < lhs.size(); ++i)
417 <<
"Compiler_Dataflow: union requires sets with the same size";
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));
430 <<
"Compiler_Dataflow: intersection requires sets with the same size";
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));
443 <<
"Compiler_Dataflow: subtraction requires sets with the same size";
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));
455 for (
size_t i = 0; i < function.
local_slots.size(); ++i)
466 for (
size_t i = 0; i < function.
local_slots.size(); ++i)
476 if (
id == 0
or id >= values.
size())
549 long long result = 0;
550#if defined(__has_builtin) && __has_builtin(__builtin_add_overflow)
557 result =
static_cast<long long>(
ul +
ur);
566 long long result = 0;
567#if defined(__has_builtin) && __has_builtin(__builtin_sub_overflow)
574 result =
static_cast<long long>(
ul -
ur);
583 long long result = 0;
584#if defined(__has_builtin) && __has_builtin(__builtin_mul_overflow)
595 const auto ul =
static_cast<unsigned long long>(
598 const auto ur =
static_cast<unsigned long long>(
612 result =
neg ? -
static_cast<long long>(
uresult)
613 :
static_cast<long long>(
uresult);
698 info.use.set(
inst.local_slot_id,
true);
703 info.def.set(
inst.local_slot_id,
true);
713 for (
size_t i = 0; i < function.
blocks.size(); ++i)
723 const auto current =
worklist.pop();
728 const auto & block = function.
blocks.access(current);
792 inst.operands.is_empty()
794 :
inst.operands.access(0)));
800 inst.operands.size() > 0
801 ?
inst.operands.access(0)
804 inst.operands.size() > 1
805 ?
inst.operands.access(1)
821 inst.operands.is_empty()
823 :
inst.operands.access(0));
844 std::ostringstream
out;
846 for (
size_t i = 0; i < set.
size(); ++i)
855 return first ?
"<none>" :
out.str();
862 std::ostringstream
out;
863 for (
size_t i = 0; i < constants.
size(); ++i)
871 return constants.
is_empty() ?
"<none>" :
out.str();
877 for (
size_t i = 0; i < function.
blocks.size(); ++i)
878 function.
blocks.access(i).predecessors.clear();
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)
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);
893 for (
size_t i = 0; i < function.
blocks.size(); ++i)
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)
899 const auto succ = function.
blocks.access(i).terminator.successors.access(j);
900 if (succ < function.
blocks.size())
901 predecessors.
access(succ).append(i);
911 for (
size_t i = 0; i < function.
blocks.size(); ++i)
912 total += function.
blocks.access(i).instructions.size();
924 and inst.local_slot_id < function.local_slots.size())
950 const auto predecessors =
955 for (
size_t i = 0; i < function.
blocks.size(); ++i)
957 function.
blocks.access(i)));
959 for (
size_t i = 0; i < function.
blocks.size(); ++i)
975 for (
size_t index = function.
blocks.size(); index > 0; --index)
977 const auto block_id = index - 1;
982 const auto & block = function.
blocks.access(block_id);
989 analysis.live_in_slots.access(succ));
999 analysis.live_out_slots.access(block_id)))
1005 analysis.live_in_slots.access(block_id)))
1018 for (
size_t block_id = 0; block_id < function.
blocks.size(); ++block_id)
1053 function.
blocks.access(block_id),
1060 analysis.assigned_in_slots.access(block_id)))
1066 analysis.assigned_out_slots.access(block_id)))
1079 for (
size_t block_id = 0; block_id < function.
blocks.size(); ++block_id)
1122 function.
blocks.access(block_id),
1123 analysis.assigned_in_slots.access(block_id),
1127 analysis.constant_in_slots.access(block_id)))
1133 analysis.constant_out_slots.access(block_id)))
1141 for (
size_t block_id = 0; block_id < function.
blocks.size(); ++block_id)
1149 function.
blocks.access(block_id),
1150 analysis.assigned_in_slots.access(block_id),
1151 analysis.constant_in_slots.access(block_id),
1153 for (
size_t i = 0; i <
findings.size(); ++i)
1184 fail(
"Dataflow analysis for '" + function.
name
1185 +
"' has mismatched reachability size");
1192 fail(
"Dataflow analysis for '" + function.
name
1193 +
"' has mismatched per-block arrays");
1198 for (
size_t block_id = 0; block_id < function.
blocks.size(); ++block_id)
1200 const auto check_set = [&fail, &function, block_id](
const char * label,
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");
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");
1226 fail(
"Dataflow analysis for '" + function.
name
1227 +
"' does not mark the entry block as reachable");
1229 for (
size_t i = 0; i <
analysis.uninitialized_reads.size(); ++i)
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");
1241 fail(
"Dataflow analysis for '" + function.
name
1242 +
"' contains an uninitialized-read finding with an invalid slot id");
1264 std::ostringstream
out;
1265 if (function ==
nullptr)
1267 out <<
"<null-dataflow-function>\n";
1271 out <<
"Dataflow(" << function->
name <<
")\n";
1273 out <<
" LocalSlots: <none>\n";
1276 out <<
" LocalSlots:\n";
1277 for (
size_t i = 0; i < function->
local_slots.size(); ++i)
1283 if (types !=
nullptr and slot.type_id != 0)
1284 out <<
": " << types->to_string(
slot.type_id);
1289 out <<
" ReachableBlocks:";
1291 for (
size_t i = 0; i <
analysis.reachable_blocks.size(); ++i)
1292 if (
analysis.reachable_blocks.access(i))
1294 out << (first ?
" " :
", ");
1302 out <<
" FoldableBranches: " <<
analysis.foldable_branch_count <<
'\n';
1303 out <<
" UninitializedReads:";
1304 if (
analysis.uninitialized_reads.is_empty())
1309 for (
size_t i = 0; i <
analysis.uninitialized_reads.size(); ++i)
1313 <<
"/I" <<
finding.instruction_index
1319 for (
size_t block_id = 0; block_id < function->
blocks.size(); ++block_id)
1321 const auto & block = function->
blocks.access(block_id);
1323 <<
" [" << block.
label <<
"]\n";
1324 out <<
" Reachable: "
1325 << ((block_id <
analysis.reachable_blocks.size()
1332 analysis.live_in_slots.access(block_id))
1336 analysis.live_out_slots.access(block_id))
1338 out <<
" AssignedIn: "
1340 analysis.assigned_in_slots.access(block_id))
1342 out <<
" AssignedOut: "
1344 analysis.assigned_out_slots.access(block_id))
1346 out <<
" ConstantsIn: "
1348 analysis.constant_in_slots.access(block_id))
1350 out <<
" ConstantsOut: "
1352 analysis.constant_out_slots.access(block_id))
1388 for (
size_t block_id = 0; block_id <
provisional.blocks.size(); ++block_id)
1390 provisional.blocks.access(block_id).predecessors.clear();
1392 if (block_id >=
analysis.reachable_blocks.size()
1399 analysis.assigned_in_slots.access(block_id),
1400 analysis.constant_in_slots.access(block_id));
1402 auto & block =
provisional.blocks.access(block_id);
1404 for (
size_t i = 0; i <=
provisional.next_value_id; ++i)
1411 and terminator.successors.size() == 2)
1413 const auto chosen =
simulation.branch_constant.bool_value
1415 : terminator.successors.access(1);
1417 terminator.condition_value = 0;
1418 terminator.successors.clear();
1419 terminator.successors.append(chosen);
1426 if (chosen <
analysis.live_in_slots.size())
1432 terminator.condition_value);
1435 terminator.return_value);
1441 for (
size_t index = block.
instructions.size(); index > 0; --index)
1443 const auto instruction_index = index - 1;
1458 keep_flags.access(instruction_index) =
static_cast<unsigned char>(
keep ? 1u : 0u);
1465 for (
size_t operand = 0; operand <
inst.operands.size(); ++operand)
1467 inst.operands.access(operand));
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)
1510 blk.predecessors.clear();
1513 for (
size_t block_id = 0; block_id <
compacted.blocks.size(); ++block_id)
1517 for (
size_t i = 0; i <
blk.terminator.successors.size(); ++i)
1578 for (
size_t i = 0; i <
ir_report.errors.size(); ++i)
1588 +
" in function '" + result.
function.
name +
"' is unreachable from entry"
1591 for (
size_t i = 0; i <
ir_report.warnings.size(); ++i)
1592 if (
ir_report.warnings.access(i).find(
"unreachable") != std::string::npos)
1599 +
"' still contains unreachable blocks");
1605 if (
analysis.foldable_branch_count != 0)
1607 +
"' still contains foldable branches");
1609 for (
size_t block_id = 0; block_id < result.
function.
blocks.size(); ++block_id)
1611 if (block_id >=
analysis.reachable_blocks.size()
1628 for (
size_t index = block.
instructions.size(); index > 0; --index)
1630 const auto instruction_index = index - 1;
1638 +
"' still contains removable code in "
1640 +
" at instruction #" + std::to_string(instruction_index));
1642 for (
size_t operand = 0; operand <
inst.operands.size(); ++operand)
1644 inst.operands.access(operand));
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.
size_t size_t int32_t value
size_t size_t int32_t * out
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().
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.
Compiler_Dataflow_Constant_Kind
Constant-lattice states used by local constant propagation.
@ Boolean
Boolean constant.
@ Unknown
A value exists but is not known to be constant.
@ Integer
Integer 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.
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.
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.
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.
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.
Compiler_Dataflow_Constant branch_constant
DynArray< Compiler_Dataflow_Constant > constant_out
Compiler_Dataflow_Bit_Set assigned_out
DynArray< Compiler_Dataflow_Constant > value_constants
Compiler_Dataflow_Bit_Set def
Compiler_Dataflow_Bit_Set use
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.
DynArray< Compiler_SSA_Instruction > instructions
Linear SSA instruction list.
std::string label
Deterministic debug label.
Compiler_SSA_Terminator terminator
Explicit block terminator.
Compiler_SSA_Value_Id return_value
Return value, when relevant.
DynArray< Compiler_SSA_Block_Id > successors
Successors in deterministic order.
Compiler_IR_Terminator_Kind kind
Terminator category.
Compiler_SSA_Value_Id condition_value
Branch condition, when relevant.
Half-open byte range inside a source file.
Lazy and scalable dynamic array implementation.