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 
15 #include <jlm/rvsdg/gamma.hpp>
16 #include <jlm/rvsdg/MatchType.hpp>
17 #include <jlm/rvsdg/theta.hpp>
18 #include <jlm/rvsdg/traverser.hpp>
19 #include <jlm/util/Statistics.hpp>
20 
21 namespace jlm::llvm
22 {
23 
25 {
26 public:
27  ~Statistics() override = default;
28 
29  explicit Statistics(const util::FilePath & sourceFile)
30  : util::Statistics(Statistics::Id::InvariantValueRedirection, sourceFile)
31  {}
32 
33  void
34  Start() noexcept
35  {
37  }
38 
39  void
40  Stop() noexcept
41  {
43  }
44 
45  static std::unique_ptr<Statistics>
46  Create(const util::FilePath & sourceFile)
47  {
48  return std::make_unique<Statistics>(sourceFile);
49  }
50 };
51 
53 
54 void
56  rvsdg::RvsdgModule & module,
58 {
59  auto statistics = Statistics::Create(module.SourceFilePath().value());
60 
61  statistics->Start();
62  redirectInRootRegion(module.Rvsdg());
63  statistics->Stop();
64 
65  statisticsCollector.CollectDemandedStatistics(std::move(statistics));
66 }
67 
68 void
70  rvsdg::RvsdgModule & rvsdgModule,
71  Configuration configuration)
72 {
74  InvariantValueRedirection invariantValueRedirection(std::move(configuration));
75  invariantValueRedirection.Run(rvsdgModule, statisticsCollector);
76 }
77 
78 void
80 {
81  // We require a topdown traversal in the root region to ensure that a lambda node is visited
82  // before its call nodes. This ensures that all invariant values are redirected in the lambda
83  // subregion before we try to detect invariant call outputs.
84  for (auto node : rvsdg::TopDownTraverser(&rvsdg.GetRootRegion()))
85  {
87  *node,
88  [this](const rvsdg::LambdaNode & lambdaNode)
89  {
90  redirectInRegion(*lambdaNode.subregion());
91  },
92  [this](const rvsdg::PhiNode & phiNode)
93  {
94  auto phiLambdaNodes = rvsdg::PhiNode::ExtractLambdaNodes(phiNode);
95  for (auto phiLambdaNode : phiLambdaNodes)
96  {
97  redirectInRegion(*phiLambdaNode->subregion());
98  }
99  },
100  [](const rvsdg::DeltaNode &)
101  {
102  // Nothing needs to be done.
103  // Delta nodes are irrelevant for invariant value redirection.
104  },
105  [](const rvsdg::SimpleNode & simpleNode)
106  {
108  simpleNode.GetOperation(),
109  [](const FunctionToPointerOperation &)
110  {
111  // Nothing needs to be done.
112  },
113  [](const PointerToFunctionOperation &)
114  {
115  // Nothing needs to be done.
116  });
117  });
118  }
119 }
120 
121 void
123 {
124  const auto isGammaNode = !!dynamic_cast<rvsdg::GammaNode *>(region.node());
125  const auto isThetaNode = !!dynamic_cast<rvsdg::ThetaNode *>(region.node());
126  const auto isLambdaNode = !!dynamic_cast<rvsdg::LambdaNode *>(region.node());
127  JLM_ASSERT(isGammaNode || isThetaNode || isLambdaNode);
128 
129  // We do not need a traverser here and can just iterate through all the nodes of a region as
130  // it is irrelevant in which order we handle the nodes.
131  for (auto & node : region.Nodes())
132  {
134  node,
135  [this](rvsdg::GammaNode & gammaNode)
136  {
137  // Ensure we redirect invariant values of all nodes in the gamma subregions first,
138  // otherwise we might not be able to redirect some of the gamma outputs.
139  redirectInSubregions(gammaNode);
140 
142  redirectGammaOutputs(gammaNode);
143  },
144  [this](rvsdg::ThetaNode & thetaNode)
145  {
146  // Ensure we redirect invariant values of all nodes in the theta subregion first,
147  // otherwise we might not be able to redirect some of the theta outputs.
148  redirectInSubregions(thetaNode);
149 
151  redirectThetaGammaOutputs(thetaNode);
152 
154  redirectThetaOutputs(thetaNode);
155  },
156  [this](rvsdg::SimpleNode & simpleNode)
157  {
159  simpleNode.GetOperation(),
160  [this, &simpleNode](const CallOperation &)
161  {
162  if (configuration_.enableCallOutputRedirection)
163  redirectCallOutputs(simpleNode);
164  },
165  [this, &simpleNode](const LoadOperation &)
166  {
167  if (configuration_.enableLoadMemoryStateRedirection)
168  redirectLoadMemoryStates(simpleNode);
169  });
170  });
171  }
172 
173  region.prune(false);
174 }
175 
176 void
178 {
179  const auto isGammaNode = !!dynamic_cast<rvsdg::GammaNode *>(&structuralNode);
180  const auto isThetaNode = !!dynamic_cast<rvsdg::ThetaNode *>(&structuralNode);
181  JLM_ASSERT(isGammaNode || isThetaNode);
182 
183  for (auto & subregion : structuralNode.Subregions())
184  {
185  redirectInRegion(subregion);
186  }
187 }
188 
189 void
191 {
192  for (auto exitVar : gammaNode.GetExitVars())
193  {
194  if (auto invariantOrigin = rvsdg::GetGammaInvariantOrigin(gammaNode, exitVar))
195  {
196  exitVar.output->divert_users(*invariantOrigin);
197  }
198  }
199 }
200 
201 void
203 {
204  for (const auto & loopVar : thetaNode.GetLoopVars())
205  {
206  // FIXME: In order to also redirect I/O state type variables, we need to know whether a loop
207  // terminates.
208  if (rvsdg::is<IOStateType>(loopVar.input->Type()))
209  continue;
210 
211  if (rvsdg::ThetaLoopVarIsInvariant(loopVar))
212  loopVar.output->divert_users(loopVar.input->origin());
213  }
214 }
215 
216 void
218 {
219  auto correlationOpt = computeThetaGammaPredicateCorrelation(thetaNode);
220  if (!correlationOpt.has_value())
221  {
222  return;
223  }
224  auto & correlation = correlationOpt.value();
225 
226  auto subregionRolesOpt = determineGammaSubregionRoles(*correlation);
227  if (!subregionRolesOpt.has_value())
228  {
229  // We could not determine the roles of the gamma subregions. Nothing can be done.
230  return;
231  }
232  auto roles = *subregionRolesOpt;
233  auto & gammaNode = correlation->gammaNode();
234 
235  auto divertLoopVar =
236  [&gammaNode](rvsdg::ThetaNode::LoopVar & loopVar, rvsdg::Output & entryVarArgument)
237  {
238  if (rvsdg::TryGetRegionParentNode<rvsdg::GammaNode>(entryVarArgument))
239  {
240  auto roleVar = gammaNode.MapBranchArgument(entryVarArgument);
241  if (auto entryVar = std::get_if<rvsdg::GammaNode::EntryVar>(&roleVar))
242  {
243  loopVar.post->divert_to(entryVar->input->origin());
244  }
245  }
246  };
247 
248  // At this point we can try to redirect the theta node loop variables
249  for (auto & loopVar : thetaNode.GetLoopVars())
250  {
251  if (loopVar.output->IsDead() && loopVar.pre->IsDead())
252  {
253  // The loop variable is completely dead. We do not need to waste any effort on it.
254  continue;
255  }
256 
257  auto & loopVarPostOperand = *loopVar.post->origin();
258  if (rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(loopVarPostOperand) != &gammaNode)
259  {
260  // The post value of the loop variable does not originate from the gamma node. Nothing can
261  // be done.
262  continue;
263  }
264  auto [branchResult, _] = gammaNode.MapOutputExitVar(loopVarPostOperand);
265 
266  if (loopVar.output->IsDead())
267  {
268  // The loop variables' output is dead, which means only its repetition value is of interest.
269  auto & entryVarArgument = *branchResult[roles.repetitionSubregion->index()]->origin();
270  divertLoopVar(loopVar, entryVarArgument);
271  }
272  else if (loopVar.pre->IsDead())
273  {
274  // The loop variables' pre value is dead, which means only its exit value is of interest.
275  auto & entryVarArgument = *branchResult[roles.exitSubregion->index()]->origin();
276  divertLoopVar(loopVar, entryVarArgument);
277  }
278  }
279 }
280 
281 void
283 {
284  JLM_ASSERT(is<CallOperation>(&callNode));
285 
286  auto callTypeClassifier = CallOperation::ClassifyCall(callNode);
287  auto callType = callTypeClassifier->GetCallType();
288 
289  // FIXME: We currently only support non-recursive direct calls. We would also like to get this
290  // working for recursive direct calls, but that requires a little bit more work as we need to be
291  // able to break the cycles between the recursive calls.
293  return;
294 
295  auto & lambdaNode =
296  rvsdg::AssertGetOwnerNode<rvsdg::LambdaNode>(callTypeClassifier->GetLambdaOutput());
297 
298  // LLVM permits code where it can happen that the number and type of arguments handed in to the
299  // call node do not agree with the number and type of lambda parameters, even though it is a
300  // direct call. See jlm::tests::LambdaCallArgumentMismatch for an example. In this case, we cannot
301  // redirect the call outputs to the call operand as the types would not align, resulting in type
302  // errors.
303  if (CallOperation::NumArguments(callNode) != lambdaNode.GetFunctionArguments().size())
304  return;
305 
306  // First, handle all call outputs where the corresponding function result is invariant
307  const auto results = lambdaNode.GetFunctionResults();
308  JLM_ASSERT(callNode.noutputs() == results.size());
309  for (size_t n = 0; n < callNode.noutputs(); n++)
310  {
311  const auto callOutput = callNode.output(n);
312 
313  auto & lambdaResult = *results[n];
314  auto origin = lambdaResult.origin();
315  if (rvsdg::TryGetRegionParentNode<rvsdg::LambdaNode>(*origin) == &lambdaNode)
316  {
317  if (auto ctxvar = lambdaNode.MapBinderContextVar(*origin))
318  {
319  // This is a bound context variable.
320  // FIXME: We would like to get this case working as well, but we need to route the origin
321  // of the respective lambda input to the subregion of the call node.
322  }
323  else
324  {
325  auto callOperand = CallOperation::Argument(callNode, origin->index())->origin();
326  callOutput->divert_users(callOperand);
327  }
328  }
329  }
330 
331  // Next, handle lambda bodies that contain memory state split and merge nodes.
332  // Memory state edges can only be routed around the call if the corresponding memory node id
333  // is invariant between the LambdaEntrySplit and the LambdaExitMerge.
334  const auto callExitSplit = CallOperation::tryGetMemoryStateExitSplit(callNode);
335  const auto callEntryMerge = CallOperation::tryGetMemoryStateEntryMerge(callNode);
336  const auto lambdaEntrySplit = tryGetMemoryStateEntrySplit(lambdaNode);
337  const auto lambdaExitMerge = tryGetMemoryStateExitMerge(lambdaNode);
338 
339  // Only continue if the call / lambda pair has all four memory state nodes
340  if (callExitSplit == nullptr || callEntryMerge == nullptr || lambdaEntrySplit == nullptr
341  || lambdaExitMerge == nullptr)
342  return;
343 
344  const auto callExitSplitOp =
345  *util::assertedCast<const CallExitMemoryStateSplitOperation>(&callExitSplit->GetOperation());
346  for (const auto memoryNodeId : callExitSplitOp.getMemoryNodeIds())
347  {
348  // TODO: This function contains special handling of the external memory node due to
349  // the possiblity of some memory nodes being compressed into the external memory node,
350  // with different functions compressing different sets of memory nodes.
351  // The merge and split nodes should ideally contain explicit information about compression,
352  // to avoid any loss of precision from assuming that missing memory nodes are still present.
353 
354  // The memory state edge representing external in the caller can not be re-routed,
355  // because it may represent several memory state edges in the callee.
356  if (memoryNodeId == aa::PointsToGraph::externalMemoryNode)
357  continue;
358 
359  // First try to find memory state edges corresponding directly
361  *lambdaExitMerge,
362  memoryNodeId);
364  *lambdaEntrySplit,
365  memoryNodeId);
366 
367  // If the memory node is not represented by a distinct memory state edge in the callee,
368  // assume that it is instead represented by the external memory node state edge.
369  if (result == nullptr)
371  *lambdaExitMerge,
373  if (argument == nullptr)
375  *lambdaEntrySplit,
377 
378  // If the lambda does not route this memory state at all, it is effectively invariant
379  if (result != nullptr && argument != nullptr)
380  {
381  // If the lambda body has an edge for this memory state, and it is not invariant, we must stop
382  if (result->origin() != argument)
383  continue;
384  }
385 
386  // If we get here, the memory state is invariant, and can be routed around the call
387  const auto output =
390  *callEntryMerge,
391  memoryNodeId);
392 
393  JLM_ASSERT(output);
394  if (!input)
395  continue;
396 
397  output->divert_users(input->origin());
398  }
399 }
400 
401 void
403 {
404  if (LoadOperation::LoadedValueOutput(loadNode).IsDead())
405  {
406  for (auto & memoryStateOutput : LoadOperation::MemoryStateOutputs(loadNode))
407  {
408  auto & memoryStateInput = LoadOperation::MapMemoryStateOutputToInput(memoryStateOutput);
409  memoryStateOutput.divert_users(memoryStateInput.origin());
410  }
411  }
412 }
413 
414 }
static jlm::util::StatisticsCollector statisticsCollector
static rvsdg::Input * tryMapMemoryNodeIdToInput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
static rvsdg::Output * tryMapMemoryNodeIdToOutput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
Call operation class.
Definition: call.hpp:251
static rvsdg::Input * Argument(const rvsdg::Node &node, const size_t n)
Definition: call.hpp:310
static rvsdg::SimpleNode * tryGetMemoryStateExitSplit(const rvsdg::Node &callNode) noexcept
Definition: call.hpp:406
static std::unique_ptr< CallTypeClassifier > ClassifyCall(const rvsdg::SimpleNode &callNode)
Classifies a call node.
Definition: call.cpp:49
static size_t NumArguments(const rvsdg::Node &node) noexcept
Definition: call.hpp:298
static rvsdg::SimpleNode * tryGetMemoryStateEntryMerge(const rvsdg::Node &callNode) noexcept
Definition: call.hpp:388
Get address of compiled function object.
static std::unique_ptr< Statistics > Create(const util::FilePath &sourceFile)
static void redirectGammaOutputs(rvsdg::GammaNode &gammaNode)
void Run(rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
static void redirectLoadMemoryStates(rvsdg::SimpleNode &loadNode)
void redirectInSubregions(rvsdg::StructuralNode &structuralNode)
static void redirectCallOutputs(rvsdg::SimpleNode &callNode)
static void createAndRun(rvsdg::RvsdgModule &rvsdgModule, Configuration configuration)
static void redirectThetaOutputs(rvsdg::ThetaNode &thetaNode)
static void redirectThetaGammaOutputs(rvsdg::ThetaNode &thetaNode)
static rvsdg::Output * tryMapMemoryNodeIdToOutput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
static rvsdg::Input * tryMapMemoryNodeIdToInput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
static rvsdg::Output & LoadedValueOutput(const rvsdg::Node &node)
Definition: Load.hpp:84
static rvsdg::Node::OutputIteratorRange MemoryStateOutputs(const rvsdg::Node &node) noexcept
Definition: Load.hpp:116
static rvsdg::Input & MapMemoryStateOutputToInput(const rvsdg::Output &output)
Definition: Load.hpp:157
Interpret pointer as callable function.
static constexpr NodeIndex externalMemoryNode
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:381
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
void prune(bool recursive)
Definition: region.cpp:323
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:574
Statistics Interface.
Definition: Statistics.hpp:31
util::Timer & GetTimer(const std::string &name)
Definition: Statistics.cpp:137
util::Timer & AddTimer(std::string name)
Definition: Statistics.cpp:158
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
void MatchType(T &obj, const Fns &... fns)
Pattern match over subclass type of given object.
std::optional< rvsdg::Output * > GetGammaInvariantOrigin(const GammaNode &gamma, const GammaNode::ExitVar &exitvar)
Determines whether a gamma exit var is path-invariant.
Definition: gamma.cpp:509
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:251