Jlm
push.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 
8 #include <jlm/llvm/opt/push.hpp>
9 #include <jlm/rvsdg/gamma.hpp>
10 #include <jlm/rvsdg/MatchType.hpp>
11 #include <jlm/rvsdg/Phi.hpp>
12 #include <jlm/rvsdg/theta.hpp>
13 #include <jlm/rvsdg/traverser.hpp>
14 #include <jlm/util/Statistics.hpp>
15 #include <jlm/util/time.hpp>
16 
17 #include <algorithm>
18 #include <deque>
19 
20 namespace jlm::llvm
21 {
22 
24 {
25 public:
26  ~Statistics() override = default;
27 
28  explicit Statistics(const util::FilePath & sourceFile)
29  : util::Statistics(Statistics::Id::PushNodes, sourceFile)
30  {}
31 
32  void
33  start(const rvsdg::Graph & graph) noexcept
34  {
37  }
38 
39  void
40  end(const rvsdg::Graph & graph) noexcept
41  {
44  }
45 
46  static std::unique_ptr<Statistics>
47  Create(const util::FilePath & sourceFile)
48  {
49  return std::make_unique<Statistics>(sourceFile);
50  }
51 };
52 
54 {
55 public:
56  explicit Context(rvsdg::LambdaNode & lambdaNode)
57  : LambdaSubregion_(lambdaNode.subregion())
58  {}
59 
61  getLambdaSubregion() const noexcept
62  {
63  return *LambdaSubregion_;
64  }
65 
66  void
67  addRegionDepth(const rvsdg::Region & region, const size_t depth) noexcept
68  {
69  JLM_ASSERT(RegionDepth_.find(&region) == RegionDepth_.end());
70  RegionDepth_[&region] = depth;
71  }
72 
73  size_t
74  getRegionDeph(const rvsdg::Region & region) const noexcept
75  {
76  return RegionDepth_.at(&region);
77  }
78 
79  void
80  addTargetRegion(const rvsdg::Node & node, rvsdg::Region & region) noexcept
81  {
82  JLM_ASSERT(TargetRegion_.find(&node) == TargetRegion_.end());
83  TargetRegion_[&node] = &region;
84  }
85 
87  getTargetRegion(const rvsdg::Node & node) const noexcept
88  {
89  return *TargetRegion_.at(&node);
90  }
91 
92  static std::unique_ptr<Context>
93  create(rvsdg::LambdaNode & lambdaNode)
94  {
95  return std::make_unique<Context>(lambdaNode);
96  }
97 
98 private:
100  std::unordered_map<const rvsdg::Region *, size_t> RegionDepth_{};
101  std::unordered_map<const rvsdg::Node *, rvsdg::Region *> TargetRegion_{};
102 };
103 
104 NodeHoisting::~NodeHoisting() noexcept = default;
105 
107  : Transformation("NodeHoisting")
108 {}
109 
110 size_t
112 {
113  if (dynamic_cast<const rvsdg::LambdaNode *>(region.node()))
114  {
115  return 0;
116  }
117 
118  const auto parentRegion = region.node()->region();
119  return context_->getRegionDeph(*parentRegion) + 1;
120 }
121 
122 bool
124 {
125  if (!is<MemoryStateType>(loopVar.output->Type()))
126  return false;
127 
128  if (loopVar.pre->nusers() != 1)
129  return false;
130 
131  const auto userNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*loopVar.pre->Users().begin());
132  const auto originNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*loopVar.post->origin());
133 
134  if (userNode != originNode)
135  return false;
136 
137  return true;
138 }
139 
142 {
143  // Handle lambda region arguments
144  if (rvsdg::TryGetRegionParentNode<rvsdg::LambdaNode>(output))
145  {
146  return *output.region();
147  }
148 
149  // Handle gamma region arguments
150  if (const auto gammaNode = rvsdg::TryGetRegionParentNode<rvsdg::GammaNode>(output))
151  {
152  if (output.Type()->Kind() == rvsdg::TypeKind::State)
153  {
154  // FIXME: This is a bit too conservative. For example, it avoids that load and store nodes are
155  // hoisted out of a gamma node, but we would only like to avoid store nodes being hoisted out.
156  // For load nodes, it is legal to hoist them out if they are not preceded by an IOBarrier.
157  return *output.region();
158  }
159 
160  const auto roleVar = gammaNode->MapBranchArgument(output);
161  if (const auto entryVar = std::get_if<rvsdg::GammaNode::EntryVar>(&roleVar))
162  {
163  return computeTargetRegion(*entryVar->input->origin());
164  }
165 
166  return *output.region();
167  }
168 
169  // Handle theta region arguments
170  if (const auto thetaNode = rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(output))
171  {
172  const auto loopVar = thetaNode->MapPreLoopVar(output);
173  if (rvsdg::ThetaLoopVarIsInvariant(loopVar))
174  {
175  return computeTargetRegion(*loopVar.input->origin());
176  }
177 
178  if (isInvariantMemoryStateLoopVar(loopVar))
179  {
180  return computeTargetRegion(*loopVar.input->origin());
181  }
182 
183  return *output.region();
184  }
185 
186  // Handle gamma outputs
187  if (const auto gammaNode = rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(output))
188  {
189  return context_->getTargetRegion(*gammaNode);
190  }
191 
192  // Handle theta outputs
193  if (const auto thetaNode = rvsdg::TryGetOwnerNode<rvsdg::ThetaNode>(output))
194  {
195  return context_->getTargetRegion(*thetaNode);
196  }
197 
198  // Handle simple node outputs
199  if (const auto node = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(output))
200  {
201  return context_->getTargetRegion(*node);
202  }
203 
204  throw std::logic_error("Unhandled output type!");
205 }
206 
209 {
210  if (node.ninputs() == 0)
211  {
212  // Nodes that can only produce states, such as UndefValueOperation, will be removed in the
213  // back-end. There is no need to hoist them.
214  JLM_ASSERT(node.noutputs() == 1);
215  return node.output(0)->Type()->Kind() == rvsdg::TypeKind::State
216  ? *node.region()
217  : context_->getLambdaSubregion();
218  }
219 
220  // Compute target regions for all the inputs of the node
221  std::vector<rvsdg::Region *> targetRegions;
222  for (auto & input : node.Inputs())
223  {
224  auto & targetRegion = computeTargetRegion(*input.origin());
225  if (&targetRegion == node.region())
226  {
227  // One of the node's predecessors cannot be hoisted, which means we can also not hoist this
228  // node
229  return *node.region();
230  }
231 
232  targetRegions.push_back(&targetRegion);
233  }
234 
235  // Compute the lowermost target region in the region tree
236  return **std::max_element(
237  targetRegions.begin(),
238  targetRegions.end(),
239  [&](const rvsdg::Region * region1, const rvsdg::Region * region2)
240  {
241  return context_->getRegionDeph(*region1) < context_->getRegionDeph(*region2);
242  });
243 }
244 
245 void
247 {
248  const auto regionDepth = computeRegionDepth(region);
249  context_->addRegionDepth(region, regionDepth);
250 
251  for (const auto node : rvsdg::TopDownConstTraverser(&region))
252  {
254  *node,
255  [&](const rvsdg::StructuralNode & structuralNode)
256  {
257  // FIXME: We currently do not allow structural nodes (gamma and theta nodes) to be hoisted
258  context_->addTargetRegion(structuralNode, *structuralNode.region());
259 
260  // Handle innermost regions
261  for (auto & subregion : structuralNode.Subregions())
262  {
263  markNodes(subregion);
264  }
265  },
266  [&](const rvsdg::SimpleNode & simpleNode)
267  {
268  rvsdg::Region & targetRegion = computeTargetRegion(simpleNode);
269  context_->addTargetRegion(*node, targetRegion);
270  },
271  []()
272  {
273  throw std::logic_error("Unhandled node type!");
274  });
275  }
276 }
277 
280 {
281  if (output.region() == &targetRegion)
282  return output;
283 
284  // Handle gamma subregion arguments
285  if (const auto gammaNode = rvsdg::TryGetRegionParentNode<rvsdg::GammaNode>(output))
286  {
287  const auto roleVar = gammaNode->MapBranchArgument(output);
288  if (const auto entryVar = std::get_if<rvsdg::GammaNode::EntryVar>(&roleVar))
289  {
290  return getOperandFromTargetRegion(*entryVar->input->origin(), targetRegion);
291  }
292  }
293 
294  // Handle theta subregion arguments
295  if (const auto thetaNode = rvsdg::TryGetRegionParentNode<rvsdg::ThetaNode>(output))
296  {
297  const auto loopVar = thetaNode->MapPreLoopVar(output);
299  return getOperandFromTargetRegion(*loopVar.input->origin(), targetRegion);
300  }
301 
302  throw std::logic_error("Unhandled output type!");
303 }
304 
305 std::vector<rvsdg::Output *>
307 {
308  std::vector<rvsdg::Output *> operands;
309  for (auto & input : node.Inputs())
310  {
311  auto & operand = getOperandFromTargetRegion(*input.origin(), targetRegion);
312  operands.push_back(&operand);
313  }
314 
315  return operands;
316 }
317 
318 void
320 {
321  auto & targetRegion = context_->getTargetRegion(node);
322  JLM_ASSERT(&targetRegion != node.region());
323 
324  const auto operands = getOperandsFromTargetRegion(node, targetRegion);
325  const auto copiedNode = node.copy(&targetRegion, operands);
326 
327  // FIXME: I really would like to have a zip function here, but C++ does not really seem to have
328  // anything better to offer
329  auto itOrg = std::begin(node.Outputs());
330  const auto endOrg = std::end(node.Outputs());
331  auto itCpy = std::begin(copiedNode->Outputs());
332  const auto endCpy = std::end(copiedNode->Outputs());
333  JLM_ASSERT(std::distance(itOrg, endOrg) == std::distance(itCpy, endCpy));
334 
335  for (; itOrg != endOrg; ++itOrg, ++itCpy)
336  {
337  auto & outputOrg = *itOrg;
338  auto & outputCpy = *itCpy;
339  auto & newOutputOrg = rvsdg::RouteToRegion(outputCpy, *node.region());
340  outputOrg.divert_users(&newOutputOrg);
341  }
342 }
343 
344 void
346 {
347  // FIXME: We a routing unnecessary values through gamma and theta nodes. We should cluster
348  // subgraphs that need to be hoisted to avoid unnecessary routing.
349  for (const auto node : rvsdg::TopDownTraverser(&region))
350  {
351  auto & targetRegion = context_->getTargetRegion(*node);
352  if (&targetRegion != node->region())
353  {
354  copyNodeToTargetRegion(*node);
355  }
356 
357  // Handle innermost regions
358  if (const auto structuralNode = dynamic_cast<rvsdg::StructuralNode *>(node))
359  {
360  for (auto & subregion : structuralNode->Subregions())
361  {
362  hoistNodes(subregion);
363  }
364  }
365  }
366 
367  region.prune(false);
368 }
369 
370 void
372 {
373  context_ = Context::create(lambdaNode);
374 
375  markNodes(*lambdaNode.subregion());
376  hoistNodes(*lambdaNode.subregion());
377 
378  context_.reset();
379 }
380 
381 void
383 {
384  for (auto & node : rvsdg::TopDownTraverser(&region))
385  {
387  *node,
388  [&](rvsdg::LambdaNode & lambdaNode)
389  {
390  hoistNodesInLambda(lambdaNode);
391  },
392  [&](rvsdg::PhiNode & phiNode)
393  {
394  hoistNodesInRootRegion(*phiNode.subregion());
395  },
396  [](rvsdg::DeltaNode &)
397  {
398  // Nothing needs to be done
399  },
400  [](rvsdg::SimpleNode &)
401  {
402  // Nothing needs to be done
403  },
404  [&]()
405  {
406  throw std::logic_error(util::strfmt("Unhandled node type: ", node->DebugString()));
407  });
408  }
409 }
410 
411 void
413 {
414  auto statistics = Statistics::Create(rvsdgModule.SourceFilePath().value());
415 
416  statistics->start(rvsdgModule.Rvsdg());
417  hoistNodesInRootRegion(rvsdgModule.Rvsdg().GetRootRegion());
418  statistics->end(rvsdgModule.Rvsdg());
419 
420  statisticsCollector.CollectDemandedStatistics(std::move(statistics));
421 }
422 }
static jlm::util::StatisticsCollector statisticsCollector
std::unordered_map< const rvsdg::Region *, size_t > RegionDepth_
Definition: push.cpp:100
Context(rvsdg::LambdaNode &lambdaNode)
Definition: push.cpp:56
size_t getRegionDeph(const rvsdg::Region &region) const noexcept
Definition: push.cpp:74
rvsdg::Region & getLambdaSubregion() const noexcept
Definition: push.cpp:61
rvsdg::Region & getTargetRegion(const rvsdg::Node &node) const noexcept
Definition: push.cpp:87
rvsdg::Region * LambdaSubregion_
Definition: push.cpp:99
void addTargetRegion(const rvsdg::Node &node, rvsdg::Region &region) noexcept
Definition: push.cpp:80
void addRegionDepth(const rvsdg::Region &region, const size_t depth) noexcept
Definition: push.cpp:67
std::unordered_map< const rvsdg::Node *, rvsdg::Region * > TargetRegion_
Definition: push.cpp:101
static std::unique_ptr< Context > create(rvsdg::LambdaNode &lambdaNode)
Definition: push.cpp:93
void end(const rvsdg::Graph &graph) noexcept
Definition: push.cpp:40
Statistics(const util::FilePath &sourceFile)
Definition: push.cpp:28
static std::unique_ptr< Statistics > Create(const util::FilePath &sourceFile)
Definition: push.cpp:47
void start(const rvsdg::Graph &graph) noexcept
Definition: push.cpp:33
Node Hoisting Transformation.
Definition: push.hpp:37
void hoistNodesInRootRegion(rvsdg::Region &region)
Definition: push.cpp:382
void hoistNodes(rvsdg::Region &region)
Definition: push.cpp:345
void hoistNodesInLambda(rvsdg::LambdaNode &lambdaNode)
Definition: push.cpp:371
void Run(rvsdg::RvsdgModule &rvsdgModule, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
Definition: push.cpp:412
~NodeHoisting() noexcept override
rvsdg::Region & computeTargetRegion(const rvsdg::Node &node) const
Definition: push.cpp:208
static std::vector< rvsdg::Output * > getOperandsFromTargetRegion(rvsdg::Node &node, rvsdg::Region &targetRegion)
Definition: push.cpp:306
std::unique_ptr< Context > context_
Definition: push.hpp:84
static bool isInvariantMemoryStateLoopVar(const rvsdg::ThetaNode::LoopVar &loopVar)
Definition: push.cpp:123
void copyNodeToTargetRegion(rvsdg::Node &node) const
Definition: push.cpp:319
void markNodes(const rvsdg::Region &region)
Definition: push.cpp:246
static rvsdg::Output & getOperandFromTargetRegion(rvsdg::Output &output, rvsdg::Region &targetRegion)
Definition: push.cpp:279
size_t computeRegionDepth(const rvsdg::Region &region) const
Definition: push.cpp:111
Delta node.
Definition: delta.hpp:129
Region & GetRootRegion() const noexcept
Definition: graph.hpp:99
Output * origin() const noexcept
Definition: node.hpp:58
Lambda node.
Definition: lambda.hpp:83
rvsdg::Region * subregion() const noexcept
Definition: lambda.hpp:138
OutputIteratorRange Outputs() noexcept
Definition: node.hpp:657
rvsdg::Region * region() const noexcept
Definition: node.hpp:761
InputIteratorRange Inputs() noexcept
Definition: node.hpp:622
NodeOutput * output(size_t index) const noexcept
Definition: node.hpp:650
size_t ninputs() const noexcept
Definition: node.hpp:609
size_t noutputs() const noexcept
Definition: node.hpp:644
virtual Node * copy(rvsdg::Region *region, const std::vector< jlm::rvsdg::Output * > &operands) const
Definition: node.cpp:369
rvsdg::Region * region() const noexcept
Definition: node.cpp:151
UsersRange Users()
Definition: node.hpp:354
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition: node.hpp:366
size_t nusers() const noexcept
Definition: node.hpp:280
A phi node represents the fixpoint of mutually recursive definitions.
Definition: Phi.hpp:46
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
const std::optional< util::FilePath > & SourceFilePath() const noexcept
Definition: RvsdgModule.hpp:73
Graph & Rvsdg() noexcept
Definition: RvsdgModule.hpp:57
SubregionIteratorRange Subregions()
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 AddMeasurement(std::string name, T value)
Definition: Statistics.hpp:177
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.
void MatchTypeWithDefault(T &obj, const Fns &... fns)
Pattern match over subclass type of given object with default handler.
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition: theta.hpp:227
Output & RouteToRegion(Output &output, Region &region)
Definition: node.cpp:381
@ State
Designate a state type.
static std::vector< jlm::rvsdg::Output * > operands(const Node *node)
Definition: node.hpp:1049
size_t ninputs(const rvsdg::Region *region) noexcept
Definition: region.cpp:838
static std::string strfmt(Args... args)
Definition: strfmt.hpp:35
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
static const char * NumRvsdgInputsAfter
Definition: Statistics.hpp:218
static const char * NumRvsdgInputsBefore
Definition: Statistics.hpp:217