Jlm
InvariantValueRedirection.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017 Nico Reißmann <nico.reissmann@gmail.com>
3  * See COPYING for terms of redistribution.
4  */
5 
13 #include <jlm/rvsdg/gamma.hpp>
14 #include <jlm/rvsdg/MatchType.hpp>
15 #include <jlm/rvsdg/theta.hpp>
16 #include <jlm/rvsdg/traverser.hpp>
17 #include <jlm/util/Statistics.hpp>
18 
19 namespace jlm::llvm
20 {
21 
23 {
24 public:
25  ~Statistics() override = default;
26 
27  explicit Statistics(const util::FilePath & sourceFile)
28  : util::Statistics(Statistics::Id::InvariantValueRedirection, sourceFile)
29  {}
30 
31  void
32  Start() noexcept
33  {
35  }
36 
37  void
38  Stop() noexcept
39  {
41  }
42 
43  static std::unique_ptr<Statistics>
44  Create(const util::FilePath & sourceFile)
45  {
46  return std::make_unique<Statistics>(sourceFile);
47  }
48 };
49 
51 
52 void
54  rvsdg::RvsdgModule & module,
55  util::StatisticsCollector & statisticsCollector)
56 {
57  auto statistics = Statistics::Create(module.SourceFilePath().value());
58 
59  statistics->Start();
60  RedirectInRootRegion(module.Rvsdg());
61  statistics->Stop();
62 
63  statisticsCollector.CollectDemandedStatistics(std::move(statistics));
64 }
65 
66 void
68 {
69  // We require a topdown traversal in the root region to ensure that a lambda node is visited
70  // before its call nodes. This ensures that all invariant values are redirected in the lambda
71  // subregion before we try to detect invariant call outputs.
72  for (auto node : rvsdg::TopDownTraverser(&rvsdg.GetRootRegion()))
73  {
75  *node,
76  [](const rvsdg::LambdaNode & lambdaNode)
77  {
78  RedirectInRegion(*lambdaNode.subregion());
79  },
80  [](const rvsdg::PhiNode & phiNode)
81  {
82  auto phiLambdaNodes = rvsdg::PhiNode::ExtractLambdaNodes(phiNode);
83  for (auto phiLambdaNode : phiLambdaNodes)
84  {
85  RedirectInRegion(*phiLambdaNode->subregion());
86  }
87  },
88  [](const rvsdg::DeltaNode &)
89  {
90  // Nothing needs to be done.
91  // Delta nodes are irrelevant for invariant value redirection.
92  },
93  [](const rvsdg::SimpleNode & simpleNode)
94  {
96  simpleNode.GetOperation(),
97  [](const FunctionToPointerOperation &)
98  {
99  // Nothing needs to be done.
100  },
101  [](const PointerToFunctionOperation &)
102  {
103  // Nothing needs to be done.
104  });
105  });
106  }
107 }
108 
109 void
111 {
112  const auto isGammaNode = !!dynamic_cast<rvsdg::GammaNode *>(region.node());
113  const auto isThetaNode = !!dynamic_cast<rvsdg::ThetaNode *>(region.node());
114  const auto isLambdaNode = !!dynamic_cast<rvsdg::LambdaNode *>(region.node());
115  JLM_ASSERT(isGammaNode || isThetaNode || isLambdaNode);
116 
117  // We do not need a traverser here and can just iterate through all the nodes of a region as
118  // it is irrelevant in which order we handle the nodes.
119  for (auto & node : region.Nodes())
120  {
121  if (auto gammaNode = dynamic_cast<rvsdg::GammaNode *>(&node))
122  {
123  // Ensure we redirect invariant values of all nodes in the gamma subregions first, otherwise
124  // we might not be able to redirect some of the gamma outputs.
125  RedirectInSubregions(*gammaNode);
126  RedirectGammaOutputs(*gammaNode);
127  }
128  else if (auto thetaNode = dynamic_cast<rvsdg::ThetaNode *>(&node))
129  {
130  // Ensure we redirect invariant values of all nodes in the theta subregion first, otherwise we
131  // might not be able to redirect some of the theta outputs.
132  RedirectInSubregions(*thetaNode);
133  RedirectThetaOutputs(*thetaNode);
134  }
135  else if (auto simpleNode = dynamic_cast<rvsdg::SimpleNode *>(&node))
136  {
137  if (is<CallOperation>(simpleNode))
138  {
139  RedirectCallOutputs(*util::assertedCast<rvsdg::SimpleNode>(&node));
140  }
141  }
142  }
143 }
144 
145 void
147 {
148  const auto isGammaNode = !!dynamic_cast<rvsdg::GammaNode *>(&structuralNode);
149  const auto isThetaNode = !!dynamic_cast<rvsdg::ThetaNode *>(&structuralNode);
150  JLM_ASSERT(isGammaNode || isThetaNode);
151 
152  for (auto & subregion : structuralNode.Subregions())
153  {
154  RedirectInRegion(subregion);
155  }
156 }
157 
158 void
160 {
161  for (auto exitvar : gammaNode.GetExitVars())
162  {
163  if (auto invariantOrigin = rvsdg::GetGammaInvariantOrigin(gammaNode, exitvar))
164  {
165  exitvar.output->divert_users(*invariantOrigin);
166  }
167  }
168 }
169 
170 void
172 {
173  redirectThetaGammaOutputs(thetaNode);
174 
175  for (const auto & loopVar : thetaNode.GetLoopVars())
176  {
177  // FIXME: In order to also redirect I/O state type variables, we need to know whether a loop
178  // terminates.
179  if (rvsdg::is<IOStateType>(loopVar.input->Type()))
180  continue;
181 
182  if (rvsdg::ThetaLoopVarIsInvariant(loopVar))
183  loopVar.output->divert_users(loopVar.input->origin());
184  }
185 }
186 
187 void
189 {
190  auto correlationOpt = computeThetaGammaPredicateCorrelation(thetaNode);
191  if (!correlationOpt.has_value())
192  {
193  return;
194  }
195  auto & correlation = correlationOpt.value();
196 
197  auto subregionRolesOpt = determineGammaSubregionRoles(*correlation);
198  if (!subregionRolesOpt.has_value())
199  {
200  // We could not determine the roles of the gamma subregions. Nothing can be done.
201  return;
202  }
203  auto roles = *subregionRolesOpt;
204  auto & gammaNode = correlation->gammaNode();
205 
206  auto divertLoopVar =
207  [&gammaNode](rvsdg::ThetaNode::LoopVar & loopVar, rvsdg::Output & entryVarArgument)
208  {
209  if (rvsdg::TryGetRegionParentNode<rvsdg::GammaNode>(entryVarArgument))
210  {
211  auto roleVar = gammaNode.MapBranchArgument(entryVarArgument);
212  if (auto entryVar = std::get_if<rvsdg::GammaNode::EntryVar>(&roleVar))
213  {
214  loopVar.post->divert_to(entryVar->input->origin());
215  }
216  }
217  };
218 
219  // At this point we can try to redirect the theta node loop variables
220  for (auto & loopVar : thetaNode.GetLoopVars())
221  {
222  if (loopVar.output->IsDead() && loopVar.pre->IsDead())
223  {
224  // The loop variable is completely dead. We do not need to waste any effort on it.
225  continue;
226  }
227 
228  auto & loopVarPostOperand = *loopVar.post->origin();
229  if (rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(loopVarPostOperand) != &gammaNode)
230  {
231  // The post value of the loop variable does not originate from the gamma node. Nothing can
232  // be done.
233  continue;
234  }
235  auto [branchResult, _] = gammaNode.MapOutputExitVar(loopVarPostOperand);
236 
237  if (loopVar.output->IsDead())
238  {
239  // The loop variables' output is dead, which means only its repetition value is of interest.
240  auto & entryVarArgument = *branchResult[roles.repetitionSubregion->index()]->origin();
241  divertLoopVar(loopVar, entryVarArgument);
242  }
243  else if (loopVar.pre->IsDead())
244  {
245  // The loop variables' pre value is dead, which means only its exit value is of interest.
246  auto & entryVarArgument = *branchResult[roles.exitSubregion->index()]->origin();
247  divertLoopVar(loopVar, entryVarArgument);
248  }
249  }
250 }
251 
252 void
254 {
255  JLM_ASSERT(is<CallOperation>(&callNode));
256 
257  auto callTypeClassifier = CallOperation::ClassifyCall(callNode);
258  auto callType = callTypeClassifier->GetCallType();
259 
260  // FIXME: We currently only support non-recursive direct calls. We would also like to get this
261  // working for recursive direct calls, but that requires a little bit more work as we need to be
262  // able to break the cycles between the recursive calls.
264  return;
265 
266  auto & lambdaNode =
267  rvsdg::AssertGetOwnerNode<rvsdg::LambdaNode>(callTypeClassifier->GetLambdaOutput());
268 
269  // LLVM permits code where it can happen that the number and type of arguments handed in to the
270  // call node do not agree with the number and type of lambda parameters, even though it is a
271  // direct call. See jlm::tests::LambdaCallArgumentMismatch for an example. In this case, we cannot
272  // redirect the call outputs to the call operand as the types would not align, resulting in type
273  // errors.
274  if (CallOperation::NumArguments(callNode) != lambdaNode.GetFunctionArguments().size())
275  return;
276 
277  // First, handle all call outputs where the corresponding function result is invariant
278  const auto results = lambdaNode.GetFunctionResults();
279  JLM_ASSERT(callNode.noutputs() == results.size());
280  for (size_t n = 0; n < callNode.noutputs(); n++)
281  {
282  const auto callOutput = callNode.output(n);
283 
284  auto & lambdaResult = *results[n];
285  auto origin = lambdaResult.origin();
286  if (rvsdg::TryGetRegionParentNode<rvsdg::LambdaNode>(*origin) == &lambdaNode)
287  {
288  if (auto ctxvar = lambdaNode.MapBinderContextVar(*origin))
289  {
290  // This is a bound context variable.
291  // FIXME: We would like to get this case working as well, but we need to route the origin
292  // of the respective lambda input to the subregion of the call node.
293  }
294  else
295  {
296  auto callOperand = CallOperation::Argument(callNode, origin->index())->origin();
297  callOutput->divert_users(callOperand);
298  }
299  }
300  }
301 
302  // Next, handle lambda bodies that contain memory state split and merge nodes.
303  // Memory state edges can only be routed around the call if the corresponding memory node id
304  // is invariant between the LambdaEntrySplit and the LambdaExitMerge.
305  const auto callExitSplit = CallOperation::tryGetMemoryStateExitSplit(callNode);
306  const auto callEntryMerge = CallOperation::tryGetMemoryStateEntryMerge(callNode);
307  const auto lambdaEntrySplit = tryGetMemoryStateEntrySplit(lambdaNode);
308  const auto lambdaExitMerge = tryGetMemoryStateExitMerge(lambdaNode);
309 
310  // Only continue if the call / lambda pair has all four memory state nodes
311  if (callExitSplit == nullptr || callEntryMerge == nullptr || lambdaEntrySplit == nullptr
312  || lambdaExitMerge == nullptr)
313  return;
314 
315  const auto callExitSplitOp =
316  *util::assertedCast<const CallExitMemoryStateSplitOperation>(&callExitSplit->GetOperation());
317  for (const auto memoryNodeId : callExitSplitOp.getMemoryNodeIds())
318  {
320  *lambdaExitMerge,
321  memoryNodeId);
323  *lambdaEntrySplit,
324  memoryNodeId);
325 
326  // If the lambda does not route this memory state at all, it is effectively invariant
327  if (result != nullptr && argument != nullptr)
328  {
329  // If the lambda body has an edge for this memory state, and it is not invariant, we must stop
330  if (result->origin() != argument)
331  continue;
332  }
333 
334  // If we get here, the memory state is invariant, and can be routed around the call
335  const auto output =
338  *callEntryMerge,
339  memoryNodeId);
340 
341  JLM_ASSERT(output);
342  if (!input)
343  continue;
344 
345  output->divert_users(input->origin());
346  }
347 }
348 
349 }
static rvsdg::Input * tryMapMemoryNodeIdToInput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
static rvsdg::Output * tryMapMemoryNodeIdToOutput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
static rvsdg::Input * Argument(const rvsdg::Node &node, const size_t n)
Definition: call.hpp:291
static rvsdg::SimpleNode * tryGetMemoryStateExitSplit(const rvsdg::Node &callNode) noexcept
Definition: call.hpp:387
static std::unique_ptr< CallTypeClassifier > ClassifyCall(const rvsdg::SimpleNode &callNode)
Classifies a call node.
Definition: call.cpp:47
static size_t NumArguments(const rvsdg::Node &node) noexcept
Definition: call.hpp:279
static rvsdg::SimpleNode * tryGetMemoryStateEntryMerge(const rvsdg::Node &callNode) noexcept
Definition: call.hpp:369
Get address of compiled function object.
static std::unique_ptr< Statistics > Create(const util::FilePath &sourceFile)
Invariant Value Redirection Optimization.
static void RedirectGammaOutputs(rvsdg::GammaNode &gammaNode)
static void RedirectCallOutputs(rvsdg::SimpleNode &callNode)
static void RedirectInRegion(rvsdg::Region &region)
static void RedirectInSubregions(rvsdg::StructuralNode &structuralNode)
void Run(rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
static void RedirectInRootRegion(rvsdg::Graph &rvsdg)
static void redirectThetaGammaOutputs(rvsdg::ThetaNode &thetaNode)
static void RedirectThetaOutputs(rvsdg::ThetaNode &thetaNode)
static rvsdg::Output * tryMapMemoryNodeIdToOutput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
static rvsdg::Input * tryMapMemoryNodeIdToInput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
Interpret pointer as callable function.
Delta node.
Definition: delta.hpp:129
Conditional operator / pattern matching.
Definition: gamma.hpp:99
std::vector< ExitVar > GetExitVars() const
Gets all exit variables for this gamma.
Definition: gamma.cpp:361
Region & GetRootRegion() const noexcept
Definition: graph.hpp:99
void divert_to(Output *new_origin)
Definition: node.cpp:64
Output * origin() const noexcept
Definition: node.hpp:58
Lambda node.
Definition: lambda.hpp:83
rvsdg::Region * subregion() const noexcept
Definition: lambda.hpp:138
size_t noutputs() const noexcept
Definition: node.hpp:644
void divert_users(jlm::rvsdg::Output *new_origin)
Definition: node.hpp:301
bool IsDead() const noexcept
Definition: node.hpp:295
A phi node represents the fixpoint of mutually recursive definitions.
Definition: Phi.hpp:46
static std::vector< rvsdg::LambdaNode * > ExtractLambdaNodes(const PhiNode &phiNode)
Definition: Phi.cpp:226
Represent acyclic RVSDG subgraphs.
Definition: region.hpp:213
rvsdg::StructuralNode * node() const noexcept
Definition: region.hpp:369
NodeRange Nodes() noexcept
Definition: region.hpp:328
const std::optional< util::FilePath > & SourceFilePath() const noexcept
Definition: RvsdgModule.hpp:73
Graph & Rvsdg() noexcept
Definition: RvsdgModule.hpp:57
NodeOutput * output(size_t index) const noexcept
Definition: simple-node.hpp:88
SubregionIteratorRange Subregions()
std::vector< LoopVar > GetLoopVars() const
Returns all loop variables.
Definition: theta.cpp:176
void CollectDemandedStatistics(std::unique_ptr< Statistics > statistics)
Definition: Statistics.hpp:563
Statistics Interface.
Definition: Statistics.hpp:31
util::Timer & GetTimer(const std::string &name)
Definition: Statistics.cpp:134
util::Timer & AddTimer(std::string name)
Definition: Statistics.cpp:155
void start() noexcept
Definition: time.hpp:54
void stop() noexcept
Definition: time.hpp:67
#define JLM_ASSERT(x)
Definition: common.hpp:16
Global memory state passed between functions.
rvsdg::SimpleNode * tryGetMemoryStateEntrySplit(const rvsdg::LambdaNode &lambdaNode) noexcept
std::optional< std::unique_ptr< ThetaGammaPredicateCorrelation > > computeThetaGammaPredicateCorrelation(rvsdg::ThetaNode &thetaNode)
std::optional< GammaSubregionRoles > determineGammaSubregionRoles(const ThetaGammaPredicateCorrelation &correlation)
rvsdg::SimpleNode * tryGetMemoryStateExitMerge(const rvsdg::LambdaNode &lambdaNode) noexcept
void MatchTypeOrFail(T &obj, const Fns &... fns)
Pattern match over subclass type of given object.
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition: theta.hpp:227
std::optional< rvsdg::Output * > GetGammaInvariantOrigin(const GammaNode &gamma, const GammaNode::ExitVar &exitvar)
Determines whether a gamma exit var is path-invariant.
Definition: gamma.cpp:470
Description of a loop-carried variable.
Definition: theta.hpp:50
rvsdg::Output * pre
Variable before iteration (input argument to subregion).
Definition: theta.hpp:58
rvsdg::Output * output
Variable at loop exit (output of theta).
Definition: theta.hpp:66
rvsdg::Input * post
Variable after iteration (output result from subregion).
Definition: theta.hpp:62
static const char * Timer
Definition: Statistics.hpp:240