diff --git a/jlm/hls/Makefile.sub b/jlm/hls/Makefile.sub index cbe3af944..e6da4633e 100644 --- a/jlm/hls/Makefile.sub +++ b/jlm/hls/Makefile.sub @@ -18,6 +18,7 @@ libhls_SOURCES = \ jlm/hls/backend/rvsdg2rhls/alloca-conv.cpp \ jlm/hls/backend/rvsdg2rhls/check-rhls.cpp \ jlm/hls/backend/rvsdg2rhls/DeadNodeElimination.cpp \ + jlm/hls/backend/rvsdg2rhls/PartialRedundancyElimination.cpp \ jlm/hls/backend/rvsdg2rhls/decouple-mem-state.cpp \ jlm/hls/backend/rvsdg2rhls/distribute-constants.cpp \ jlm/hls/backend/rvsdg2rhls/GammaConversion.cpp \ @@ -62,6 +63,7 @@ libhls_HEADERS = \ jlm/hls/backend/rvsdg2rhls/alloca-conv.hpp \ jlm/hls/backend/rvsdg2rhls/check-rhls.hpp \ jlm/hls/backend/rvsdg2rhls/DeadNodeElimination.hpp \ + jlm/hls/backend/rvsdg2rhls/PartialRedundancyElimination.hpp \ jlm/hls/backend/rvsdg2rhls/decouple-mem-state.hpp \ jlm/hls/backend/rvsdg2rhls/distribute-constants.hpp \ jlm/hls/backend/rvsdg2rhls/GammaConversion.hpp \ diff --git a/jlm/llvm/Makefile.sub b/jlm/llvm/Makefile.sub index 85986a0d5..23e2675bc 100644 --- a/jlm/llvm/Makefile.sub +++ b/jlm/llvm/Makefile.sub @@ -63,11 +63,13 @@ libllvm_SOURCES = \ jlm/llvm/opt/alias-analyses/Steensgaard.cpp \ jlm/llvm/opt/CommonNodeElimination.cpp \ jlm/llvm/opt/DeadNodeElimination.cpp \ + jlm/llvm/opt/gvn.cpp \ jlm/llvm/opt/IfConversion.cpp \ jlm/llvm/opt/inlining.cpp \ jlm/llvm/opt/InvariantValueRedirection.cpp \ jlm/llvm/opt/LoadChainSeparation.cpp \ jlm/llvm/opt/LoopUnswitching.cpp \ + jlm/llvm/opt/PartialRedundancyElimination.cpp \ jlm/llvm/opt/PredicateCorrelation.cpp \ jlm/llvm/opt/pull.cpp \ jlm/llvm/opt/push.cpp \ @@ -84,6 +86,7 @@ libllvm_HEADERS = \ \ jlm/llvm/opt/unroll.hpp \ jlm/llvm/opt/DeadNodeElimination.hpp \ + jlm/llvm/opt/gvn.hpp \ jlm/llvm/opt/inlining.hpp \ jlm/llvm/opt/CommonNodeElimination.hpp \ jlm/llvm/opt/push.hpp \ @@ -113,6 +116,7 @@ libllvm_HEADERS = \ jlm/llvm/opt/InvariantValueRedirection.hpp \ jlm/llvm/opt/LoadChainSeparation.hpp \ jlm/llvm/opt/LoopUnswitching.hpp \ + jlm/llvm/opt/PartialRedundancyElimination.hpp \ jlm/llvm/opt/PredicateCorrelation.hpp \ jlm/llvm/opt/RvsdgTreePrinter.hpp \ jlm/llvm/opt/ScalarEvolution.hpp \ diff --git a/jlm/llvm/ir/operators/MemoryStateOperations.cpp b/jlm/llvm/ir/operators/MemoryStateOperations.cpp index 1e14dd144..c97b60823 100644 --- a/jlm/llvm/ir/operators/MemoryStateOperations.cpp +++ b/jlm/llvm/ir/operators/MemoryStateOperations.cpp @@ -39,8 +39,13 @@ MemoryStateMergeOperation::NormalizeSingleOperand( const MemoryStateMergeOperation &, const std::vector & operands) { - if (operands.size() == 1) - return operands; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#pragma GCC diagnostic ignored "-Wstringop-overflow" + if (operands.size() == 1){ + return {operands}; + } +#pragma GCC diagnostic pop return std::nullopt; } @@ -157,8 +162,13 @@ MemoryStateJoinOperation::NormalizeSingleOperand( const MemoryStateJoinOperation &, const std::vector & operands) { - if (operands.size() == 1) - return operands; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#pragma GCC diagnostic ignored "-Wstringop-overflow" + if (operands.size() == 1){ + return {operands}; + } +#pragma GCC diagnostic pop return std::nullopt; } diff --git a/jlm/llvm/opt/PartialRedundancyElimination.cpp b/jlm/llvm/opt/PartialRedundancyElimination.cpp new file mode 100644 index 000000000..5470c39f4 --- /dev/null +++ b/jlm/llvm/opt/PartialRedundancyElimination.cpp @@ -0,0 +1,594 @@ +/* + * Copyright 2025 Lars Astrup Sundt + * See COPYING for terms of redistribution. + */ + +#define TR_CMD "\033" +#define TR_RESET TR_CMD "[0m" + +#define TR_FG(r,g,b) TR_CMD "[38;2;" #r ";" #g ";" #b "m" +#define TR_RED TR_FG(255,64,64) +#define TR_GREEN TR_FG(64, 255, 64) + +#define TR_PURPLE TR_FG(128,0,128) +#define TR_YELLOW TR_FG(255, 255, 64) +#define TR_ORANGE TR_FG(255, 128, 0) +#define TR_BLUE TR_FG(64, 64, 255) +#define TR_PINK TR_FG(255,128,128) +#define TR_CYAN TR_FG(64, 255, 255) +#define TR_GRAY TR_FG(52,52,52) + +#include "jlm/rvsdg/gamma.hpp" +#include "jlm/rvsdg/lambda.hpp" +#include "jlm/rvsdg/delta.hpp" +#include "jlm/rvsdg/MatchType.hpp" +#include "jlm/rvsdg/node.hpp" +#include "jlm/rvsdg/structural-node.hpp" +#include "jlm/rvsdg/theta.hpp" +#include "jlm/util/GraphWriter.hpp" +#include "jlm/llvm/ir/operators/IntegerOperations.hpp" +#include "PartialRedundancyElimination.hpp" +#include "jlm/rvsdg/control.hpp" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jlm/rvsdg/binary.hpp" +#include "jlm/util/common.hpp" +#include "jlm/llvm/ir/types.hpp" +#include "jlm/llvm/opt/gvn.hpp" +#include + +#include + + + +namespace jlm::rvsdg +{ +class Operation; +} + +/** This might be moved to util if proven useful elsewhere **/ +static int indentation_level = 0; + +inline std::string ind() +{ + std::string acc = ""; + for (int i = 0;i + DbgPrint& operator<<(const T& message) { + if (verbose) {std::cout << message;} + return *this; + } + + DbgPrint& operator<<(std::ostream& (*o)(std::ostream&)) { + if (verbose) {std::cout << o;} + return *this; + } +}; + +static DbgPrint dbg_out(false); + +/** -------------------------------------------------------------------------------------------- **/ + +namespace jlm::llvm +{ + +/** -------------------------------------------------------------------------------------------- **/ + +PartialRedundancyElimination::~PartialRedundancyElimination() noexcept {} +PartialRedundancyElimination::PartialRedundancyElimination() : + jlm::rvsdg::Transformation("PartialRedundancyElimination"), + stat_theta_count(0), + stat_gamma_count(0) +{ + +} + +void PartialRedundancyElimination::TraverseTopDownRecursively(rvsdg::Region& reg, void(*cb)(PartialRedundancyElimination* pe, rvsdg::Node* node)) +{ + IndentMan indenter = IndentMan(); + for (rvsdg::Node* node : rvsdg::TopDownTraverser(®)) + { + cb(this, node); + MatchType(*node, [this,cb](rvsdg::StructuralNode& sn) + { + for (auto& reg : sn.Subregions()) + { + this->TraverseTopDownRecursively(reg, cb); + dbg_out << ind() << TR_GRAY << "..........................." << TR_RESET << std::endl; + } + }); + } +} + +void PartialRedundancyElimination::GVN(rvsdg::Region& root) +{ + gvn_mode_thetas_ = ThetaMode::GVN_FIND_FIXED_POINT; + GVN_VisitRegion(root); + gvn_mode_thetas_ = ThetaMode::GVN_FINALIZE; + GVN_VisitRegion(root); +} + +void +PartialRedundancyElimination::Run( + rvsdg::RvsdgModule & module, + util::StatisticsCollector & statisticsCollector) +{ + auto & rvsdg = module.Rvsdg(); + auto& root = rvsdg.GetRootRegion(); + this->TraverseTopDownRecursively(root, initialize_stats); + + GVN(root); + + if (gvn_.stat_collisions){ + throw std::runtime_error("gvn_.stat_collisions"); + } +} + +/** -------------------------------------------------------------------------------------------- **/ +static size_t reg_counter = 0; +void PartialRedundancyElimination::dump_region(rvsdg::Node* node) +{ + std::string name = node->DebugString() + std::to_string(node->GetNodeId()); + + MatchType(*node, [&name](rvsdg::StructuralNode& sn) + { + for (auto& reg : sn.Subregions()) + { + auto my_graph_writer = jlm::util::graph::Writer(); + + jlm::llvm::LlvmDotWriter my_dot_writer; + my_dot_writer.WriteGraphs(my_graph_writer , reg, false); + + std::string full_name = "reg_dump/" + name+"__"+std::to_string(reg_counter)+"__.dot"; reg_counter++; + dbg_out<< TR_RED<stat_theta_count++; + }); + + MatchType(*node, [&pe](rvsdg::GammaNode& _){ + pe->stat_gamma_count++; + }); +} + +void PartialRedundancyElimination::GVN_VisitRegion(rvsdg::Region& reg) +{ + IndentMan indenter = IndentMan(); + for (rvsdg::Node* node : rvsdg::TopDownTraverser(®)){GVN_VisitNode(node);} +} + +void PartialRedundancyElimination::GVN_VisitAllSubRegions(rvsdg::Node* node) +{ + MatchType(*node,[this](rvsdg::StructuralNode& sn){ + for (auto& reg : sn.Subregions()){GVN_VisitRegion(reg);} + }); +} + +void PartialRedundancyElimination::GVN_VisitLeafNode(rvsdg::Node* node) +{ + dbg_out << ind() << "Leaf node: " << node->DebugString() << node->GetNodeId() << std::endl; + // Treats state edges just like any other value + if (node->ninputs() && node->noutputs()){ + auto op_opaque = gvn_.FromStr( node->DebugString() ); + auto hash_call_out = gvn_.FromStr("hash_call_out"); + gvn_.Op(op_opaque); + for (size_t i = 0 ; i ninputs() ; i++){ + auto from = node->input(i)->origin(); + gvn_.Arg( GVNOrWarn(from, node) ); + } + auto hash_all_inputs = gvn_.End(); + for (size_t i = 0 ; i < node->noutputs() ; i++){ + auto arg_pos = gvn_.FromWord(i); + auto g_out = gvn_.Op(hash_call_out).Arg(hash_all_inputs).Arg(arg_pos).End(); + RegisterGVN( node->output(i), g_out ); + } + } + + MatchType(node->GetOperation(), + [this, node](const jlm::llvm::IntegerConstantOperation& iconst){ + size_t value = 0; + auto istr = iconst.Representation().str(); + for (size_t i = 0 ; i < istr.length() ; i++){ + value += istr[i] == '1' ? 1 << i : 0; + } + RegisterGVN( node->output(0), gvn_.FromWord(value) ); + }, + [this, node](const jlm::rvsdg::BinaryOperation& binop){ + using namespace jlm::rvsdg::gvn; + JLM_ASSERT(node->ninputs() >= 2); + auto op = gvn_.FromStr(binop.debug_string()); + if (binop.debug_string() == "IAdd"){op = GVN_OP_ADDITION; } + if (binop.debug_string() == "IMul"){op = GVN_OP_MULTIPLY; } + if (binop.debug_string() == "INe"){op = GVN_OP_NEQ; } + if (binop.debug_string() == "IEq"){op = GVN_OP_EQ; } + + auto a = GVNOrWarn( node->input(0)->origin(), node); + auto b = GVNOrWarn( node->input(1)->origin(), node); + + RegisterGVN(node->output(0), gvn_.Op(op).Arg(a).Arg(b).End()); + } + ); + + for (size_t i = 0; i < node->noutputs(); i++){ + if (output_to_gvn_.find(node->output(i)) == output_to_gvn_.end()){ + if (node->DebugString() == std::string("undef")){ + RegisterGVN( node->output(i), rvsdg::gvn::GVN_NO_VALUE ); + } + /*auto& op = node->GetOperation(); + if ( rvsdg::is_ctlconstant_op( op ) ){ + RegisterGVN( node->output(i), rvsdg::gvn::GVN_NO_VALUE ); + }*/ + } + } + + MatchType(node->GetOperation(), [this, node](const rvsdg::MatchOperation& mop){ + using namespace jlm::rvsdg::gvn; + auto pred = GVNOrPanic( node->input(0)->origin(), node ); + if (pred == GVN_TRUE) {pred = 1;} + if (pred == GVN_FALSE){pred = 0;} + if (jlm::rvsdg::gvn::GVN_IsSmallValue(pred)){ + RegisterGVN( node->output(0), mop.alternative(static_cast(pred)) ); + } + }); +} + +void PartialRedundancyElimination::GVN_VisitGammaNode(rvsdg::Node * node) +{ + using namespace jlm::rvsdg::gvn; + + MatchType(*node, [this,node](rvsdg::GammaNode& gn){ + dbg_out << ind() << TR_YELLOW << node->DebugString() << node->GetNodeId() << TR_RESET << std::endl; + + //Route gvn values into alternatives + for (auto br : gn.GetEntryVars()){ + auto out = br.input->origin(); + for (auto into_branch : br.branchArgument){ + RegisterGVN(into_branch, GVNOrWarn(out, node)); + } + } + + auto mv_edge = gn.GetMatchVar().input->origin(); + auto mv_val = GVNOrWarn( mv_edge, node ); + for (auto mv : gn.GetMatchVar().matchContent){ + RegisterGVN(mv, mv_val); + } + + // -------------------------------------------------------------------------------------------- + GVN_VisitAllSubRegions(node); + // -------------------------------------------------------------------------------------------- + + auto GAMMA_VARIABLE_OUT = gvn_.FromStr("GAMMA_VARIABLE_OUT"); + auto BRANCHES_CONDITIONALLY = gvn_.FromStr("CONDITIONAL_BRANCHING"); + + auto entry_vars = gn.GetEntryVars(); + auto exit_vars = gn.GetExitVars(); + auto match_var = gn.GetMatchVar(); + auto predicate = GVNOrPanic(gn.predicate()->origin(), node); + + for (auto ev : exit_vars){ + auto any_branch_value = GVN_NO_VALUE; + auto value_from_branch_always_taken = BRANCHES_CONDITIONALLY; + gvn_.Op(GVN_OP_ANY_ORDERED); // Returns input if all inputs are the same. + for (size_t b = 0; b < ev.branchResult.size(); b++){ + auto leaving_branch = ev.branchResult[b]; + auto from_inner = GVNOrZero(leaving_branch->origin()); + gvn_.Arg( from_inner ); + + // Special case all branches return the same value + any_branch_value = from_inner; + // Special case. Match variable was known. + // --------------------- check if predicate is known and maps to this branch ------------- + auto match_node = rvsdg::TryGetOwnerNode( *(match_var.input->origin()) ); + if (match_node){ + MatchType(match_node->GetOperation(), [&value_from_branch_always_taken, predicate, b, from_inner](const rvsdg::MatchOperation& mop){ + if (jlm::rvsdg::gvn::GVN_IsSmallValue(predicate) && mop.alternative(static_cast(predicate)) == b){ + value_from_branch_always_taken = from_inner; + } + }); + } + // --------------------- + } + auto branches_merged = gvn_.End(); + + RegisterGVN( ev.output, + gvn_.Op(GAMMA_VARIABLE_OUT).Arg(predicate).Arg(branches_merged).End() + ); + + // If all branches output the same value + if (any_branch_value == branches_merged){ RegisterGVN(ev.output, any_branch_value); } + // If the match variable has a known value + if (value_from_branch_always_taken != BRANCHES_CONDITIONALLY){ RegisterGVN(ev.output, value_from_branch_always_taken); } + } + }); +} + +static rvsdg::gvn::GVN_Val mergeIntoLV(rvsdg::gvn::GVN_Manager& gvn, rvsdg::gvn::GVN_Val olderValue, rvsdg::gvn::GVN_Val newerValue, rvsdg::gvn::GVN_Val prism) +{ + if (olderValue == newerValue){return olderValue;} + auto MERGE_ON_LOOP_ENTRY = gvn.FromStr("MERGE_ON_LOOP_ENTRY"); + return gvn.Op(MERGE_ON_LOOP_ENTRY).Arg(prism).Arg(olderValue).Arg(newerValue).End(); +} + +void PartialRedundancyElimination::GVN_VisitThetaNode(rvsdg::Node * node) +{ + MatchType(*node, [this,node](rvsdg::ThetaNode& tn){ + using namespace jlm::rvsdg::gvn; + auto LOOP_EXIT = gvn_.FromStr("LOOP_EXIT"); + auto OP_PRISM = gvn_.FromStr("prism"); + + auto lv = tn.GetLoopVars(); + + /** ----------------------------------- LOAD INPUTS INTO PRE.disruptors ------------------- */ + + if (thetas_.find(node) == thetas_.end()){ + thetas_.insert({node, ThetaData()}); + thetas_[node].prism = 0; // This value capture the behavior of thetas given their context + thetas_[node].stat_iteration_count = 0; + for (auto v : tn.GetLoopVars()){ + thetas_[node].pre.Add( GVNOrWarn( v.input->origin() , node ) ); + thetas_[node].post.Add( 0 ); + } + }else{ + ///// Only called for nested loops. Not tested yet. + { + dbg_out << TR_RED << "Warning nested loops not tested yet." << TR_RESET << std::endl; + // Similar to loop back at the end of simple loops. + for (size_t i = 0; i < lv.size(); i++){ + thetas_[node].pre.elements[i].disruptor = mergeIntoLV( + gvn_, + thetas_[node].pre.elements[i].partition, + GVNOrWarn( lv[i].input->origin(), node ), + thetas_[node].prism + ); + } + } + } + + auto& td = thetas_[node]; + + // Inputs from upstreams have now been placed in pre .disruptor fields + while (thetas_[node].pre.Fracture() || td.stat_iteration_count == 0){ + // ================= PERFORM GVN IN LOOP BODY ======================== + for (size_t i = 0; i < lv.size(); i++){ + RegisterGVN( lv[i].pre, td.pre.elements[i].disruptor ); + } + + GVN_VisitAllSubRegions(node); + + for (size_t i = 0; i < lv.size(); i++){ + td.post.elements[i].disruptor = GVNOrWarn( lv[i].post->origin(), node ); + } + // ================= END LOOP BODY ==================================== + + // Merge input and outputs for each loop variable in order to identify + // the most updated partitions + for (size_t i = 0; i < lv.size(); i++){ + auto input = td.pre.elements[i].disruptor; + auto output = td.post.elements[i].disruptor; + auto merged_io = gvn_.Op(GVN_OP_ANY_ORDERED).Arg(input).Arg(output).End(); + td.post.elements[i].partition = merged_io; + } + /// This hash depends on the mapping between inputs to ouputs as well as the predicate + /// Does not depend on order of loop variables or duplicate loop variables + GVN_Val predicate = GVNOrPanic( tn.predicate()->origin(), node ); + td.prism = gvn_.Op(OP_PRISM).Arg(predicate).FromPartitions(td.post).End(); + + // Why hashing solely with the predicate might not work: + // Update in parallel + // i -> i + 1 + a , i control the predicate. + // a -> a + 2 + b + // b -> b + 3 + c + // c -> c + 4 + K , K eventually affects i + // however, it reaches a fixed point in 2 iterations + + // Note: this doesn't use rvsdg::ThetaLoopVarIsInvariant() + // Because we pass in data from outside the theta it might be possible + // to detect more invariance such as from + // gammas inside the loop with constant match variables + // However, using rvsdg::ThetaLoopVarIsInvariant() will work as well + + for (size_t i = 0; i < lv.size(); i++){ + // input and output for one loop body visit are stored in pre and post, .disruptor + bool was_invariant = td.pre.elements[i].disruptor == td.post.elements[i].disruptor; + + if (was_invariant){ + // no need to update the input for the next loop iteration + // , however store away whether the value is invariant for when computing outputs from the theta + td.post.elements[i].partition = GVN_INVARIANT; + }else{ + auto lv_old = td.pre.elements[i].disruptor; + auto lv_newer = td.post.elements[i].disruptor; + + // hashing with prism here prevents accidental capture of values from outer loops. + auto g = mergeIntoLV(gvn_, lv_old, lv_newer, td.prism); + td.pre.elements[i].disruptor = g; + td.post.elements[i].partition = g; + } + } + td.stat_iteration_count++; + } + + // After the loop body has been evaluated until partitions reach a fixed point + + // Note: inner loops are only re-evaluated when the outer loop passes in new data which + // either causes a re-partitioning or detects a change in an initial value for the first time (fracture). + // That is if a value invariant with respect to the inner loop changes in the outer loop. + + /** ----------------------------------- COMPUTE LOOP OUTPUTS -------------------------------------- */ + + // ============================================================================================= + //This is only required for nested loops + for (size_t i = 0; i < lv.size(); i++){ + thetas_[node].pre.elements[i].disruptor = GVNOrWarn( lv[i].input->origin(), node );; + } + auto hash_from_inputs = gvn_.Op(LOOP_EXIT).OneDisruptorPerPartition(td.pre).End(); + // ============================================================================================= + + for (size_t i = 0; i < lv.size(); i++){ + auto inv = thetas_[node].post.elements[i].partition == GVN_INVARIANT; + auto input_for_lv = GVNOrWarn(lv[i].input->origin(), node); + if (inv){ + RegisterGVN(lv[i].output, input_for_lv); + }else{ + RegisterGVN( + lv[i].output, + gvn_.Op(LOOP_EXIT) + .Arg(thetas_[node].post.elements[i].partition) + .Arg(hash_from_inputs) + .Arg(input_for_lv) + .End() + ); + } + } + }); +} + +void PartialRedundancyElimination::GVN_FinalizeThetaNode(rvsdg::Node * node) +{ + MatchType(*node, [this,node](rvsdg::ThetaNode& tn){ + using namespace jlm::rvsdg::gvn; + auto lv = tn.GetLoopVars(); + + // Visit each loop body one more time, but let the most recent values for invariant values from + // outer loops inside. + for (size_t i = 0; i < lv.size(); i++){ + auto from_outer = GVNOrWarn( lv[i].input->origin(), node ); + auto merged = gvn_.Op(GVN_OP_ANY_ORDERED).Arg(from_outer).Arg(thetas_[node].pre.elements[i].disruptor).End(); + if (thetas_[node].post.elements[i].partition == GVN_INVARIANT){ + RegisterGVN( lv[i].pre, from_outer ); + }else{ + RegisterGVN( lv[i].pre, merged ); + } + } + GVN_VisitAllSubRegions(node); + thetas_[node].stat_iteration_count++; + // Do not update output of thetas, just the gvn values in the loop body. + }); +} + +void PartialRedundancyElimination::GVN_VisitLambdaNode(rvsdg::Node * node) +{ + MatchType(*node, [this,node](rvsdg::LambdaNode& ln){ + size_t i = 0; + for (auto arg : ln.GetFunctionArguments()){ + auto g = gvn_.FromStr("Param:" + std::to_string(i)); + RegisterGVN(arg, g ); i++; + } + for (auto arg : ln.GetContextVars()) + { + auto from = arg.input->origin(); + RegisterGVN(arg.inner, GVNOrWarn(from, node)); + } + dbg_out << ind() << TR_PURPLE << node->DebugString() << node->GetNodeId() << TR_RESET << std::endl; + GVN_VisitAllSubRegions(node); + }); +} + +void PartialRedundancyElimination::GVN_VisitNode(rvsdg::Node* node) +{ + MatchTypeWithDefault(*node, + [this,node](rvsdg::DeltaNode& _){ + dbg_out << ind() << TR_CYAN << node->DebugString() << node->GetNodeId() << TR_RESET << std::endl; + GVN_VisitAllSubRegions(node); + }, + [this,node](rvsdg::ThetaNode& _){ + switch (gvn_mode_thetas_){ + case ThetaMode::GVN_FIND_FIXED_POINT: GVN_VisitThetaNode(node); break; + case ThetaMode::GVN_FINALIZE: GVN_FinalizeThetaNode(node); break; + }; + }, + [this,node](rvsdg::GammaNode& _){ + GVN_VisitGammaNode(node); + }, + [this,node](rvsdg::LambdaNode& _){ + GVN_VisitLambdaNode(node); + }, + //DEFAULT + [this, node](){ + GVN_VisitLeafNode(node); + } + ); +} + +void PartialRedundancyElimination::dump_node(PartialRedundancyElimination* pe, rvsdg::Node* node) +{ + using namespace jlm::rvsdg::gvn; + dbg_out << ind() << TR_BLUE << node->DebugString() << "<"<GetNodeId() <<">"<< TR_RESET; + + MatchType(*node, [&pe, &node](rvsdg::LambdaNode& ln){ + dbg_out <GVNOrZero(arg); + } + dbg_out << TR_RESET; + }); + + for (size_t i = 0; i < node->ninputs(); i++){ + dbg_out << TR_YELLOW; + dbg_out << " : " << to_string(pe->GVNOrZero( node->input(i)->origin() )); + } + + dbg_out << TR_GRAY << " => "; + for (size_t i = 0; i < node->noutputs(); i++){ + dbg_out << TR_RED; + dbg_out << " : " << to_string(pe->GVNOrZero( node->output(i) )); + } + + dbg_out << std::endl; +} + +} + + diff --git a/jlm/llvm/opt/PartialRedundancyElimination.hpp b/jlm/llvm/opt/PartialRedundancyElimination.hpp new file mode 100644 index 000000000..edd1ba3e2 --- /dev/null +++ b/jlm/llvm/opt/PartialRedundancyElimination.hpp @@ -0,0 +1,132 @@ +/* + * Copyright 2025 Lars Astrup Sundt + * See COPYING for terms of redistribution. + */ + +#ifndef JLM_LLVM_OPT_PARTIAL_REDUNDANCY_ELIMINATION_HPP +#define JLM_LLVM_OPT_PARTIAL_REDUNDANCY_ELIMINATION_HPP + +#include "gvn.hpp" +#include "jlm/llvm/ir/operators/IntegerOperations.hpp" +#include "jlm/rvsdg/MatchType.hpp" +#include "jlm/rvsdg/traverser.hpp" +#include +#include +#include +#include +#include +#include + +namespace jlm::rvsdg +{ +class DeltaNode; +class GammaNode; +class Graph; +class LambdaNode; +class Output; +class StructuralNode; +class ThetaNode; +class Region; +} + +namespace jlm::llvm{ + +struct ThetaData +{ + ThetaData():stat_iteration_count(0), prism(0){} + size_t stat_iteration_count; + jlm::rvsdg::gvn::GVN_Val prism; + jlm::rvsdg::gvn::BrittlePrism pre; + jlm::rvsdg::gvn::BrittlePrism post; +}; + +/** \brief Partial Redundancy Elimination + * + * A pass for doing partial redundancy analysis and elimination + */ +class PartialRedundancyElimination final : public jlm::rvsdg::Transformation +{ + class Context; + class Statistics; + +public: + ~PartialRedundancyElimination() noexcept override; + + PartialRedundancyElimination(); + + PartialRedundancyElimination(const PartialRedundancyElimination &) = delete; + PartialRedundancyElimination(PartialRedundancyElimination &&) = delete; + + PartialRedundancyElimination & + operator=(const PartialRedundancyElimination &) = delete; + PartialRedundancyElimination & + operator=(PartialRedundancyElimination &&) = delete; + + void + Run(jlm::rvsdg::RvsdgModule & module, jlm::util::StatisticsCollector & statisticsCollector) override; + + + +private: + enum class ThetaMode{ + GVN_FIND_FIXED_POINT, + GVN_FINALIZE, + }; + ThetaMode gvn_mode_thetas_ = ThetaMode::GVN_FIND_FIXED_POINT; + void TraverseTopDownRecursively(rvsdg::Region& reg, void(*cb)(PartialRedundancyElimination* pe, rvsdg::Node* node)); + + static void dump_region(rvsdg::Node* node); + static void dump_node( PartialRedundancyElimination *pe, rvsdg::Node* node); + static void initialize_stats( PartialRedundancyElimination *pe, rvsdg::Node* node); + + size_t stat_theta_count; + size_t stat_gamma_count; + std::unordered_map thetas_; + + std::unordered_map< rvsdg::Output *, rvsdg::gvn::GVN_Val> output_to_gvn_; + void RegisterGVN(rvsdg::Output * output, rvsdg::gvn::GVN_Val gvn){ + output_to_gvn_[output] = gvn; //overwrites old values + } + + rvsdg::gvn::GVN_Manager gvn_; + inline rvsdg::gvn::GVN_Val GVNOrZero(rvsdg::Output* edge){ + if (output_to_gvn_.find(edge) != output_to_gvn_.end()){ + return output_to_gvn_[edge]; + } + return rvsdg::gvn::GVN_NO_VALUE; + } + + inline rvsdg::gvn::GVN_Val GVNOrWarn(rvsdg::Output* edge, rvsdg::Node* ctx_node){ + if (output_to_gvn_.find(edge) != output_to_gvn_.end()){ + return output_to_gvn_[edge]; + } + + std::cout << "Logic error: missing input for edge" + ctx_node->DebugString() + std::to_string(ctx_node->GetNodeId()); + + return rvsdg::gvn::GVN_NO_VALUE; + } + + inline rvsdg::gvn::GVN_Val GVNOrPanic(rvsdg::Output* edge, rvsdg::Node* ctx_node){ + if (output_to_gvn_.find(edge) != output_to_gvn_.end()){ + return output_to_gvn_[edge]; + } + throw std::runtime_error("Logic error: missing input for edge" + ctx_node->DebugString() + std::to_string(ctx_node->GetNodeId())); + } + + /// ----------------------------------------------------------- + + void GVN(rvsdg::Region& root); + void GVN_VisitRegion(rvsdg::Region& reg); + void GVN_VisitAllSubRegions(rvsdg::Node* node); + void GVN_VisitNode(rvsdg::Node* node); + void GVN_VisitGammaNode(rvsdg::Node* node); + void GVN_VisitThetaNode(rvsdg::Node* tn); + void GVN_FinalizeThetaNode(rvsdg::Node * node); + void GVN_VisitLambdaNode(rvsdg::Node* ln); + void GVN_VisitLeafNode(rvsdg::Node* node); +}; + +} + + +#endif diff --git a/jlm/llvm/opt/gvn.cpp b/jlm/llvm/opt/gvn.cpp new file mode 100644 index 000000000..40306d564 --- /dev/null +++ b/jlm/llvm/opt/gvn.cpp @@ -0,0 +1,244 @@ +/* + * Copyright 2025 Lars Astrup Sundt + * See COPYING for terms of redistribution. + */ + +#include "jlm/llvm/opt/gvn.hpp" + +bool jlm::rvsdg::gvn::gvn_verbose = false; +namespace jlm::rvsdg::gvn { + + void GVN_Manager::Test0() + { + GVN_Manager gm; + auto x = gm.Leaf(); + auto y = gm.Leaf(); + { + if (gm.Op(GVN_OP_ADDITION).Arg(x).Arg(0).End() != x) { + throw std::runtime_error("Addition error, x expected"); + } + if (gm.Op(GVN_OP_ADDITION).Arg(3).Arg(4).Arg(2).Arg(3).End() != 12) { + throw std::runtime_error("Addition error, 12"); + } + if (gm.Op(GVN_OP_MULTIPLY).Arg(3).Arg(4).Arg(2).Arg(3).End() != 72) { + throw std::runtime_error("Addition error, 72"); + } + } + { + auto xyx = gm.Op(GVN_OP_ADDITION).Arg(x).Arg(y).Arg(x).End(); + auto xxyy = gm.Op(GVN_OP_ADDITION).Arg(x).Arg(y).Arg(x).Arg(y).End(); + if (gm.Op(GVN_OP_ADDITION).Arg(xyx).Arg(y).End() != xxyy){ + throw std::runtime_error("Addition error x+y+x + y != x+y+x+y"); + } + } + { + auto xx = gm.Op(GVN_OP_MULTIPLY).Arg(x).Arg(x).End(); + auto xxyy = gm.Op(GVN_OP_MULTIPLY).Arg(x).Arg(y).Arg(x).Arg(y).End(); + if (gm.Op(GVN_OP_MULTIPLY).Arg(y).Arg(xx).Arg(y).End() != xxyy){ + throw std::runtime_error("Multiplication error, yxxy != xxyy"); + } + } + { + auto x0 = gm.Op(GVN_OP_MULTIPLY).Arg(x).Arg(0).End(); + auto x1 = gm.Op(GVN_OP_MULTIPLY).Arg(x).Arg(1).End(); + //check identities + if (gm.Op(GVN_OP_MULTIPLY).Arg(x).Arg(0).End() != x0 || x0 != 0){ + throw std::runtime_error("Multiplication error: x*0"); + } + if (gm.Op(GVN_OP_MULTIPLY).Arg(x).Arg(1).End() != x1){ + throw std::runtime_error("Multiplication error: x*1"); + } + } + } + void GVN_Manager::Test1() + { + GVN_Manager gm; + const char* str = "foo"; + GVN_Val a = gm.FromPtr(str); + GVN_Val b = gm.FromPtr(str); + if (a != b) { + throw std::runtime_error("FromPtr failed"); + } + } + void GVN_Manager::Test2() + { + GVN_Manager gm; + GVN_Val x = gm.Leaf(); + GVN_Val y = gm.Leaf(); + + GVN_Val xx = gm.Op(GVN_OP_ANY_ORDERED).Args({x,x}); + GVN_Val xy = gm.Op(GVN_OP_ANY_ORDERED).Args({x,y}); + GVN_Val xy_2 = gm.Op(GVN_OP_ANY_ORDERED).Args({x,y}); + + if (xy != xy_2) { + std::cout << to_string(xy) << to_string(xy_2) << std::endl; + throw std::runtime_error("Values should be stable"); + } + + if (xy == xx) {throw std::runtime_error("Values should be different");} + if (xx != x) {throw std::runtime_error("Values should be same");} + } + + void GVN_Manager::Test3() + { + GVN_Manager gm; + auto a = gm.Leaf(); + auto b = gm.FromWord(88); + if ( gm.Op(GVN_OP_ADDITION).Args({a,b}) != gm.Op(GVN_OP_ADDITION).Args({b,GVN_IGNORE,a,GVN_IGNORE}) ) { + throw std::runtime_error("Should have ignored some args"); + } + } + + void GVN_Manager::Test4() + { + GVN_Manager gm; + GVN_Val four = gm.FromWord(4); + GVN_Val zero = gm.FromWord(0); + GVN_Val too_big = gm.FromWord(0xffffffffffffffull); + GVN_Val same_too_big = gm.FromWord(0xffffffffffffffull); + if (four != 4){throw std::runtime_error("Small value not non-symbolic: " + to_string(four));} + if (zero != 0){throw std::runtime_error("Small value not non-symbolic: " + to_string(zero));} + if (!(too_big & GVN_IS_SYMBOLIC)){throw std::runtime_error("Big constants should be symbols");} + if (too_big != same_too_big){throw std::runtime_error("FromWord should be referentially transparent.");} + } + + void BrittlePrism::Test0() + { + std::vector v = {77,128,128,77,77}; + BrittlePrism p0(v); + if (gvn_verbose){p0.dump();} + p0.OrderByPartition(); + if (gvn_verbose){std::cout << "----------partitions-------------"< + * See COPYING for terms of redistribution. + */ + + +#ifndef JLM_LLVM_OPT_GVN_H +#define JLM_LLVM_OPT_GVN_H + +#include +#include +#include +#include +#include +#include + +namespace jlm::rvsdg::gvn { + extern bool gvn_verbose; + typedef uint64_t GVN_Val; + + constexpr GVN_Val GVN_SMALL_VALUE = 0xFFFFFFFF; // Must not collide with flags below. + + // ------------------------------------------------------------------------------- + /* Flags stored as part of GVN values */ + constexpr GVN_Val GVN_IS_SYMBOLIC = 1ull << 33; /* Not a small integer */ + constexpr GVN_Val GVN_HAS_DEPS = 1ull << 34; /* \brief : By setting a single bit for internal nodes it becomes impossible for leaf and internal nodes to have the same hash making code simpler. */ + constexpr GVN_Val GVN_CONST_SYMBOL = 1ull << 35; + + constexpr GVN_Val GVN_MASK_INHERIT = GVN_IS_SYMBOLIC; + constexpr GVN_Val GVN_MASK = GVN_IS_SYMBOLIC | GVN_HAS_DEPS | GVN_CONST_SYMBOL; + + inline bool GVN_IsSmallValue(GVN_Val v) {return (v & GVN_SMALL_VALUE) == v;} + inline bool GVN_ValueHasDeps(GVN_Val v) {return v & GVN_HAS_DEPS;} + + // ------------------------------------------------------------------------------- + constexpr GVN_Val GVN_PREDEFS = GVN_CONST_SYMBOL | GVN_IS_SYMBOLIC; + + /* GLOBAL OPERATION */ + constexpr GVN_Val GVN_OP_ANY_ORDERED = GVN_PREDEFS | 1; + constexpr GVN_Val GVN_OP_ADDITION = GVN_PREDEFS | 2; + constexpr GVN_Val GVN_OP_MULTIPLY = GVN_PREDEFS | 3; + constexpr GVN_Val GVN_OP_EQ = GVN_PREDEFS | 4; // N-ary checks if all values are the same + constexpr GVN_Val GVN_OP_NEQ = GVN_PREDEFS | 5; // N-ary checks is two values are distinct + constexpr GVN_Val GVN_INVARIANT = GVN_PREDEFS | 6; + constexpr GVN_Val GVN_TOMBSTONE = (~0ull & ~GVN_MASK) | GVN_PREDEFS; // Largest possible value + + /* GLOBAL CONSTANTS */ + constexpr GVN_Val GVN_NO_VALUE = GVN_PREDEFS | GVN_CONST_SYMBOL | 100; + constexpr GVN_Val GVN_TRUE = GVN_PREDEFS | GVN_CONST_SYMBOL | 101; + constexpr GVN_Val GVN_FALSE = GVN_PREDEFS | GVN_CONST_SYMBOL | 102; + + /* SPECIAL VALUES */ + constexpr GVN_Val GVN_IGNORE = GVN_PREDEFS | GVN_CONST_SYMBOL | 103; + + inline std::string to_string(GVN_Val v) { + auto n = static_cast(v); + + std::string s = std::to_string(n); + if (s.length() > 8){ + s = s.substr(0,3) + "..." + s.substr(s.length() - 3, s.length()); + } + + //if ((v & GVN_HAS_DEPS) == 0) {s += "";} + if (v & GVN_IS_SYMBOLIC){s += "$";} + // Constant predefined symbols + if (v == GVN_TRUE) {s = "TRUE";} + if (v == GVN_FALSE) {s = "FALSE";} + if (v == GVN_NO_VALUE) {s = "NO_VALUE";} + if (v == GVN_OP_ADDITION) {s = "+";} + if (v == GVN_OP_MULTIPLY) {s = "*";} + return s; + } + + struct BrittlePrismEle { + GVN_Val partition; + GVN_Val disruptor; + GVN_Val original_partition; + size_t original_position; + }; + + constexpr const char* ORD_ORIGINAL = "order:original"; + constexpr const char* ORD_PARTITION = "order:partition"; + constexpr const char* ORD_DISRUPTOR = "order:disruptor"; + constexpr const char* ORD_PARTITION_DISRUPTOR = "order:partition>disruptor"; + constexpr const char* ORD_DISRUPTOR_PARTITION = "order:disruptor>partition"; + + class BrittlePrism { + private: + const char* current_ordering; + + public: + bool did_shatter; + size_t fracture_count; + std::vector elements; + + /// --------------------------- ORDERINGS ----------------------------------------------------------------- + explicit BrittlePrism(std::vector base) : did_shatter(false), fracture_count(0) + { + // set gvn values and original indices + // partition same as unique values + for (size_t i = 0; i < base.size(); i++) { + elements.emplace_back(BrittlePrismEle{base[i], base[i], base[i],i}); + } + current_ordering = ORD_ORIGINAL; + } + BrittlePrism() : did_shatter(false), fracture_count(0){ + current_ordering = ORD_ORIGINAL; + } + void Add(GVN_Val v){ + elements.emplace_back(BrittlePrismEle{v, v, v,elements.size()}); + } + + void OrderByPartition() { + std::sort(elements.begin(), elements.end(), + [](BrittlePrismEle& a, BrittlePrismEle& b) { + return a.partition < b.partition; + } + ); + current_ordering = ORD_PARTITION; + } + + void OrderByDisruptor() { + std::sort(elements.begin(), elements.end(), + [](BrittlePrismEle& a, BrittlePrismEle& b) { + return a.disruptor < b.disruptor; + } + ); + current_ordering = ORD_DISRUPTOR; + } + + void OrderByOriginal() + { + std::sort(elements.begin(), elements.end(), + [](BrittlePrismEle& a, BrittlePrismEle& b) { + return a.original_position < b.original_position; + } + ); + current_ordering = ORD_ORIGINAL; + } + void OrderByPartitionThenDisruptor() + { + std::sort(elements.begin(), elements.end(), + [](BrittlePrismEle& a, BrittlePrismEle& b) { + if (a.partition == b.partition){return a.disruptor < b.disruptor;} + return a.partition < b.partition; + } + ); + current_ordering = ORD_PARTITION_DISRUPTOR; + } + void OrderByDisruptorThenPartition() + { + std::sort(elements.begin(), elements.end(), + [](BrittlePrismEle& a, BrittlePrismEle& b) { + if (a.disruptor == b.disruptor){return a.partition < b.partition;} + return a.disruptor < b.disruptor; + } + ); + current_ordering = ORD_DISRUPTOR_PARTITION; + } + /// --------------------------- SPANS ----------------------------------------------------------------- + size_t SpanPartition(size_t start) + { + size_t span = 1; + GVN_Val partition = elements[start].partition; + for (size_t i = start+1; i < elements.size(); i++) { + if (elements[i].partition == partition) {span++;}else{break;} + } + return span; + } + + size_t SpanDisruptor(size_t start) const + { + size_t span = 1; + GVN_Val disruptor = elements[start].disruptor; + for (size_t i = start+1; i < elements.size(); i++) { + if (elements[i].disruptor == disruptor) {span++;}else{break;} + } + return span; + } + + size_t SpanDisruptorAndPartition(size_t start) + { + size_t span = 1; + GVN_Val partition = elements[start].partition; + GVN_Val disruptor = elements[start].disruptor; + for (size_t i = start+1; i < elements.size(); i++) { + auto e = elements[i]; + if (e.partition == partition && e.disruptor == disruptor) {span++;}else{break;} + } + return span; + } + + /// --------------------------- INSPECT ----------------------------------------------------------------- + + void dump() const + { + std::cout << "gvn_partition -- " << current_ordering << std::endl; + + for (size_t i = 0; i < elements.size(); i++) { + auto e = elements[i]; + std::cout << "["< + static void EachPartition(BrittlePrism& p, Fn cb) + { + p.OrderByPartition(); + size_t it = 0; + while (it < p.elements.size()) { + size_t p_span = p.SpanPartition(it); + cb(p.elements[it], p_span); + it += p_span; + } + } + + template + static void Shatter(BrittlePrism& p, Fn reassign) { + if (p.did_shatter){return;} + // Call this when a loop body has been invoked too many times. + // This should assign unique partitions to all elements + // Provable loop invariant variables can be set to GVN_NULL + p.OrderByOriginal(); + for (size_t i = 0; i < p.elements.size(); i++) { + reassign(p.elements[i], i); + } + p.OrderByPartition(); + for (size_t i = 1; i < p.elements.size(); i++) { + if (p.elements[i].partition && (p.elements[i].partition == p.elements[i-1].partition) ) { + throw std::runtime_error("Shatter failed. All partitions must be unique."); + } + } + p.did_shatter = true; + } + + bool Fracture() + { + // Partition again based on disruptors + // Either because a single partition faces distinct disruptors or when + // the disruptor does equal the original value. + bool did_fracture = false; + OrderByPartitionThenDisruptor(); + + size_t it = 0; + while (it < elements.size()) { + size_t pspan = SpanPartition(it); + size_t gspan = SpanDisruptorAndPartition(it); + size_t p_end = it + pspan; + + if (pspan == gspan) { + // Check if value changes for first time + while (it < p_end) { + BrittlePrismEle& e = elements[it]; + if (e.original_partition == e.partition && e.disruptor != e.original_partition) { + elements[it].partition = e.disruptor; + did_fracture = true; + } + it++; + } + }else { + // Partition was given multiple different disruptors + did_fracture = true; + while (it < p_end) { + BrittlePrismEle& e = elements[it]; + if (e.partition != e.disruptor && e.disruptor != e.original_partition) { + elements[it].partition = e.disruptor; + } + it++; + } + } + } + if (did_fracture) { + fracture_count++; + if (fracture_count > elements.size() * 2) { + throw std::runtime_error("Brittle prism invariant broken. Possible missing updates to by reassign callback."); + } + } + OrderByOriginal(); + return did_fracture; + } + + static void Test0(); + static void Test1(); + }; + + ///////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////// + + class GVN_Manager{ + + struct GVN_Deps { + GVN_Deps():op(0){} + GVN_Val op; + std::vector > args; + void push(GVN_Val v, size_t count){args.emplace_back(v, count);} + void push(GVN_Val v) {args.emplace_back(v, 1);} + + void dump() { + std::cout << "op: " << op << std::endl; + for (size_t i = 0; i < args.size(); ++i) { + std::cout << " [" < builder_args_; + std::optional builder_op_; + std::optional builder_flags_; + + std::unordered_map< std::string, GVN_Val > str_to_gvn_; // Convenience map + std::unordered_map< const void*, GVN_Val > ptr_to_gvn_; // Convenience map + std::unordered_map< size_t, GVN_Val > word_to_gvn_; // Convenience map + std::unordered_map< GVN_Val, std::optional > gvn_; + + public: + size_t stat_collisions; + size_t stat_leaf_collisions; + size_t stat_ca_too_big; + + size_t max_ca_size; + + GVN_Manager() : stat_collisions(0), stat_leaf_collisions(0), stat_ca_too_big(0), max_ca_size(32) { + // Add constant symbols to the table of all values such that + // values cannot collide. + DefineConst(GVN_OP_ANY_ORDERED); + DefineConst(GVN_OP_ADDITION); + DefineConst(GVN_OP_MULTIPLY); + DefineConst(GVN_OP_EQ); + DefineConst(GVN_OP_NEQ); + + DefineConst(GVN_NO_VALUE); + DefineConst(GVN_TRUE); + DefineConst(GVN_FALSE); + + DefineConst(GVN_IGNORE); + DefineConst(GVN_INVARIANT); + + DefineConst(~0ull); + } + + GVN_Val Leaf() { + return Leaf(0); + } + GVN_Val Leaf(GVN_Val flags) { + auto g = SymGen(flags); + gvn_.insert({g,std::nullopt}); + return g; + } + GVN_Manager& Op(GVN_Val op) { + if (builder_op_){throw std::runtime_error("Multiple calls to Op(...) or missing End()");} + builder_op_ = op; + builder_flags_ = 0; + return *this; + } + GVN_Manager& Arg(GVN_Val arg) { + builder_args_.emplace_back(arg); + return *this; + } + + GVN_Val Args(const std::vector& args) { + if (args.empty()){throw std::runtime_error("Args cannot be empty. Make empty argument lists explicit by passing a dummy leaf.");} + builder_args_ = args; + return End(); + } + + GVN_Val End() { + if (!builder_op_) {throw std::runtime_error("Operator not specified");} + if (builder_args_.empty()){throw std::runtime_error("Args not specified");} + GVN_Val g = Create(*builder_op_, builder_args_); + builder_flags_ = std::nullopt; + builder_args_.clear(); + builder_op_ = std::nullopt; + return g; + } + GVN_Val FromStr(const std::string& str) {return FromStr(str, 0);} + GVN_Val FromStr(const std::string s, uint64_t flags) + { + flags |= GVN_IS_SYMBOLIC; + auto q = str_to_gvn_.find(s); + if (q == str_to_gvn_.end()) { + str_to_gvn_.insert({s,Leaf(flags)}); + q = str_to_gvn_.find(s); + } + if (q == str_to_gvn_.end()) {throw std::runtime_error("Expected some value");} + if ((q->second & GVN_MASK) != flags) { + throw std::runtime_error("Inconsistent flags for literal: " + s); + } + return str_to_gvn_[s]; + } + GVN_Val FromWord(size_t w) + { + if (w <= GVN_SMALL_VALUE) {return w;} + auto q = word_to_gvn_.find(w); + if (q == word_to_gvn_.end()) { + word_to_gvn_.insert({w,Leaf(0)}); + q = word_to_gvn_.find(w); + } + if (q == word_to_gvn_.end()) {throw std::runtime_error("Expected some value");} + return word_to_gvn_[w]; + } + GVN_Val FromPtr(const void* p) {return FromPtr(p, 0);} + GVN_Val FromPtr(const void* p, uint64_t flags) + { + flags |= GVN_IS_SYMBOLIC; + auto q = ptr_to_gvn_.find(p); + if (q == ptr_to_gvn_.end()) { + ptr_to_gvn_.insert({p,Leaf(flags)}); + q = ptr_to_gvn_.find(p); + } + if (q == ptr_to_gvn_.end()) {throw std::runtime_error("Expected some value");} + if ((q->second & GVN_MASK) != flags) { + throw std::runtime_error("Inconsistent flags for gvn generated from pointer: " + std::to_string(reinterpret_cast(p))); + } + return ptr_to_gvn_[p]; + } + + GVN_Manager& FromPartitions(BrittlePrism& brittle) + { + // Call Arg( ) once for each partition + { + brittle.OrderByPartition(); + size_t i = 0; + while (i < brittle.elements.size()) + { + Arg(brittle.elements[i].partition); + i += brittle.SpanPartition(i); + } + brittle.OrderByOriginal(); + } + return *this; + } + GVN_Manager& OneDisruptorPerPartition(BrittlePrism& brittle) + { + // Call Arg( ) once for each partition + { + brittle.OrderByPartition(); + size_t i = 0; + while (i < brittle.elements.size()) + { + Arg(brittle.elements[i].disruptor); // !!!! + i += brittle.SpanPartition(i); + } + brittle.OrderByOriginal(); + } + return *this; + } + + private: + void DefineConst(GVN_Val v) + { + if (gvn_.find(v) != gvn_.end()) { + throw std::runtime_error("Duplicate constant definition."); + } + gvn_[v] = std::nullopt; + } + GVN_Val Create(GVN_Val op, const std::vector& args) { + if (args.empty()){throw std::runtime_error("Logic error: GVN operator applied to zero args.");} + GVN_Deps new_gvn = GVN_Deps(); + new_gvn.op = op; + // Initialize new_gvn.args + // Either a copy of args or count of operator cluster leaves + + for (auto a : args) { + if (a != GVN_IGNORE) { + new_gvn.push(a); + } + } + + std::pair pr = CalculateHash(new_gvn); + + GVN_Val v = pr.first; + bool is_older_value = pr.second; + if (is_older_value) {return v;} //Note: if the hash is a small number return here. + + // The memory usage for large dags of ca type operations might be too large + if (new_gvn.args.size() > max_ca_size) { + stat_ca_too_big++; + return Leaf(); + } + + bool did_linear_probe = false; + // Check if gvn is already in use, compare with existing gvn if so + while ( gvn_.find(v) != gvn_.end() ) { + if (DepsEqual(*gvn_[v] , new_gvn)){break;} + v = (v & ~GVN_SMALL_VALUE) | ((v + 1) & GVN_SMALL_VALUE); + did_linear_probe = true; + throw std::runtime_error("True hash collision detected."); + } + + // ----------------- commit --------------------- + if (gvn_.find(v) == gvn_.end()) { + if (did_linear_probe) { stat_collisions++; } + gvn_.insert({v,new_gvn}); + } + return v; + } + + private: + GVN_Val SymGen() { + return SymGen(0); + } + GVN_Val SymGen(GVN_Val flags) { + GVN_Val g = 0; + do{ + g = random() & ~GVN_MASK; + g |= flags | GVN_IS_SYMBOLIC; + }while(gvn_.find(g) != gvn_.end()); + return g; + } + void NormalizeCa(GVN_Deps& deps) + { + // Flatten + for (size_t i = 0; i < deps.args.size(); i++) { + auto leaf = deps.args[i].first; + if (leaf & GVN_HAS_DEPS) { + auto leaf_deps = *gvn_[leaf]; + if (leaf_deps.op == deps.op) { + for (auto lf : leaf_deps.args) {deps.args.emplace_back(lf);} + deps.args[i].first = GVN_TOMBSTONE; //mark for deletion + } + } + if (deps.op == GVN_OP_ADDITION && deps.args[i].first == 0) {deps.args[i].first = GVN_TOMBSTONE;} + if (deps.op == GVN_OP_MULTIPLY && deps.args[i].first == 1) {deps.args[i].first = GVN_TOMBSTONE;} //delete zeroes + } + std::sort(deps.args.begin(), deps.args.end()); + // Coalesce + for (size_t i = 1; i < deps.args.size(); i++) + { + if (deps.args[i ].first == deps.args[i-1].first){ + deps.args[i].second += deps.args[i-1].second; + deps.args[i-1].first = GVN_TOMBSTONE; + } + } + std::sort(deps.args.begin(), deps.args.end()); + // Collect + while (deps.args.size() && deps.args[ deps.args.size() - 1 ].first == GVN_TOMBSTONE) {deps.args.pop_back();} + } + + std::pair CalculateHash(GVN_Deps& deps) { + // Return a gvn value based on operator and arguments + // The second element of the pair is true if the value cannot collide + if (deps.op == GVN_OP_ADDITION || deps.op == GVN_OP_MULTIPLY){NormalizeCa(deps);} + + // The lower bits are used to store properties for operations and + // keep track of context dependence of values + GVN_Val flags = 0; + for (auto arg : deps.args) { + flags |= arg.first & GVN_MASK_INHERIT; + } + + GVN_Val v = 0; + + switch (deps.op) { + case GVN_OP_NEQ: { + // Note: NEQ cannot assume two symbol values are different. + bool must_be_different = false; + bool all_same = true; + for (size_t i = 1; i #include #include +#include +#include +#include #include #include #include @@ -419,7 +422,9 @@ JlmOptCommand::CreateTransformation(JlmOptCommandLineOptions::OptimizationId opt case JlmOptCommandLineOptions::OptimizationId::CommonNodeElimination: return std::make_shared(); case JlmOptCommandLineOptions::OptimizationId::DeadNodeElimination: - return std::make_shared(); + return std::make_unique(); + case JlmOptCommandLineOptions::OptimizationId::PartialRedundancyElimination: + return std::make_unique(); case JlmOptCommandLineOptions::OptimizationId::FunctionInlining: return std::make_shared(); case JlmOptCommandLineOptions::OptimizationId::IfConversion: diff --git a/jlm/tooling/CommandLine.cpp b/jlm/tooling/CommandLine.cpp index e3aa4adef..fea9f087b 100644 --- a/jlm/tooling/CommandLine.cpp +++ b/jlm/tooling/CommandLine.cpp @@ -107,6 +107,8 @@ JlmOptCommandLineOptions::FromCommandLineArgumentToOptimizationId( OptimizationId::CommonNodeElimination }, { OptimizationCommandLineArgument::DeadNodeElimination_, OptimizationId::DeadNodeElimination }, + { OptimizationCommandLineArgument::PartialRedundancyElimination_, + OptimizationId::PartialRedundancyElimination }, { OptimizationCommandLineArgument::FunctionInlining_, OptimizationId::FunctionInlining }, { OptimizationCommandLineArgument::IfConversion_, OptimizationId::IfConversion }, { OptimizationCommandLineArgument::InvariantValueRedirection_, @@ -146,6 +148,8 @@ JlmOptCommandLineOptions::ToCommandLineArgument(OptimizationId optimizationId) OptimizationCommandLineArgument::CommonNodeElimination_ }, { OptimizationId::DeadNodeElimination, OptimizationCommandLineArgument::DeadNodeElimination_ }, + { OptimizationId::PartialRedundancyElimination, + OptimizationCommandLineArgument::PartialRedundancyElimination_ }, { OptimizationId::FunctionInlining, OptimizationCommandLineArgument::FunctionInlining_ }, { OptimizationId::IfConversion, OptimizationCommandLineArgument::IfConversion_ }, { OptimizationId::InvariantValueRedirection, @@ -513,6 +517,9 @@ JlcCommandLineParser::ParseCommandLineArguments(int argc, const char * const * a CreateStatisticsOption( util::Statistics::Id::DeadNodeElimination, "Collect dead node elimination pass statistics."), + CreateStatisticsOption( + util::Statistics::Id::PartialRedundancyElimination, + "PRE under construction. Currently performs GVN."), CreateStatisticsOption( util::Statistics::Id::FunctionInlining, "Collect function inlining pass statistics."), @@ -843,6 +850,7 @@ JlmOptCommandLineParser::ParseCommandLineArguments(int argc, const char * const JlmOptCommandLineOptions::OptimizationId::AASteensgaardRegionAware; auto commonNodeElimination = JlmOptCommandLineOptions::OptimizationId::CommonNodeElimination; auto deadNodeElimination = JlmOptCommandLineOptions::OptimizationId::DeadNodeElimination; + auto PartialRedundancyElimination = JlmOptCommandLineOptions::OptimizationId::PartialRedundancyElimination; auto functionInlining = JlmOptCommandLineOptions::OptimizationId::FunctionInlining; auto ifConversion = JlmOptCommandLineOptions::OptimizationId::IfConversion; auto invariantValueRedirection = @@ -883,6 +891,10 @@ JlmOptCommandLineParser::ParseCommandLineArguments(int argc, const char * const deadNodeElimination, JlmOptCommandLineOptions::ToCommandLineArgument(deadNodeElimination), "Dead Node Elimination"), + ::clEnumValN( + PartialRedundancyElimination, + JlmOptCommandLineOptions::ToCommandLineArgument(PartialRedundancyElimination), + "Partial Redundancy Elimination. Eliminate computations found redundant along some execution paths"), ::clEnumValN( functionInlining, JlmOptCommandLineOptions::ToCommandLineArgument(functionInlining), diff --git a/jlm/tooling/CommandLine.hpp b/jlm/tooling/CommandLine.hpp index 4e8a77732..3426beb8c 100644 --- a/jlm/tooling/CommandLine.hpp +++ b/jlm/tooling/CommandLine.hpp @@ -71,6 +71,7 @@ class JlmOptCommandLineOptions final : public CommandLineOptions AASteensgaardRegionAware, CommonNodeElimination, DeadNodeElimination, + PartialRedundancyElimination, FunctionInlining, IfConversion, InvariantValueRedirection, @@ -85,6 +86,7 @@ class JlmOptCommandLineOptions final : public CommandLineOptions ThetaGammaInversion, LastEnumValue // must always be the last enum value, used for iteration + , }; JlmOptCommandLineOptions( @@ -215,6 +217,7 @@ class JlmOptCommandLineOptions final : public CommandLineOptions inline static const char * AaSteensgaardRegionAware_ = "AASteensgaardRegionAware"; inline static const char * CommonNodeElimination_ = "CommonNodeElimination"; inline static const char * DeadNodeElimination_ = "DeadNodeElimination"; + inline static const char * PartialRedundancyElimination_ = "PartialRedundancyElimination"; inline static const char * FunctionInlining_ = "FunctionInlining"; inline static const char * IfConversion_ = "IfConversion"; inline static const char * InvariantValueRedirection_ = "InvariantValueRedirection"; diff --git a/jlm/util/Statistics.hpp b/jlm/util/Statistics.hpp index d979be135..a517dd56c 100644 --- a/jlm/util/Statistics.hpp +++ b/jlm/util/Statistics.hpp @@ -43,6 +43,7 @@ class Statistics ControlFlowRecovery, DataNodeToDelta, DeadNodeElimination, + PartialRedundancyElimination, FunctionInlining, IfConversion, InvariantValueRedirection,