Jlm
pull.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/pull.hpp>
9 #include <jlm/rvsdg/gamma.hpp>
10 #include <jlm/rvsdg/traverser.hpp>
11 #include <jlm/util/Statistics.hpp>
12 #include <jlm/util/time.hpp>
13 
14 namespace jlm::llvm
15 {
16 
18 {
19 public:
20  ~Statistics() override = default;
21 
22  explicit Statistics(const util::FilePath & sourceFile)
23  : util::Statistics(Statistics::Id::PullNodes, sourceFile)
24  {}
25 
26  void
27  start(const rvsdg::Graph & graph) noexcept
28  {
31  }
32 
33  void
34  end(const rvsdg::Graph & graph) noexcept
35  {
38  }
39 
40  static std::unique_ptr<Statistics>
41  Create(const util::FilePath & sourceFile)
42  {
43  return std::make_unique<Statistics>(sourceFile);
44  }
45 };
46 
47 static bool
48 empty(const rvsdg::GammaNode * gamma)
49 {
50  for (size_t n = 0; n < gamma->nsubregions(); n++)
51  {
52  if (gamma->subregion(n)->numNodes() != 0)
53  return false;
54  }
55 
56  return true;
57 }
58 
69 static bool
70 isSingleSuccessor(const rvsdg::Node & node, const rvsdg::Node & singleSuccessor)
71 {
72  for (auto & output : node.Outputs())
73  {
74  for (const auto & user : output.Users())
75  {
76  const auto successor = rvsdg::TryGetOwnerNode<rvsdg::Node>(user);
77  if (successor != &singleSuccessor)
78  return false;
79  }
80  }
81 
82  return true;
83 }
84 
85 static void
87 {
88  /* collect operands */
89  std::vector<std::vector<jlm::rvsdg::Output *>> operands(gamma->nsubregions());
90  for (size_t i = 0; i < node->ninputs(); i++)
91  {
92  auto ev = gamma->AddEntryVar(node->input(i)->origin());
93  std::size_t index = 0;
94  for (auto input : ev.branchArgument)
95  operands[index++].push_back(input);
96  }
97 
98  /* copy node into subregions */
99  for (size_t r = 0; r < gamma->nsubregions(); r++)
100  {
101  auto copy = node->copy(gamma->subregion(r), operands[r]);
102 
103  /* redirect outputs */
104  for (size_t o = 0; o < node->noutputs(); o++)
105  {
106  for (const auto & user : node->output(o)->Users())
107  {
108  auto entryvar = std::get<rvsdg::GammaNode::EntryVar>(gamma->MapInput(user));
109  entryvar.branchArgument[r]->divert_users(copy->output(o));
110  }
111  }
112  }
113 }
114 
115 static void
117 {
118  JLM_ASSERT(isSingleSuccessor(*node, *gamma));
119 
120  /* remove entry variables and node */
121  std::vector<rvsdg::GammaNode::EntryVar> entryvars;
122  for (size_t n = 0; n < node->noutputs(); n++)
123  {
124  for (auto & user : node->output(n)->Users())
125  {
126  entryvars.push_back(std::get<rvsdg::GammaNode::EntryVar>(gamma->MapInput(user)));
127  }
128  }
129  gamma->RemoveEntryVars(entryvars);
130  remove(node);
131 }
132 
133 void
135 {
136  /* FIXME: This is inefficient. We can do better. */
137  auto evs = gamma->GetEntryVars();
138  size_t index = 0;
139  while (index < evs.size())
140  {
141  const auto & ev = evs[index];
142  auto node = rvsdg::TryGetOwnerNode<rvsdg::Node>(*ev.input->origin());
143  auto tmp = rvsdg::TryGetOwnerNode<rvsdg::Node>(*gamma->predicate()->origin());
144  if (node && tmp != node && isSingleSuccessor(*node, *gamma))
145  {
146  pullin_node(gamma, node);
147 
148  cleanup(gamma, node);
149 
150  evs = gamma->GetEntryVars();
151  index = 0;
152  }
153  else
154  {
155  index++;
156  }
157  }
158 }
159 
162 {
163  std::function<void(const rvsdg::Node &, util::HashSet<rvsdg::Node *> &)> collect =
164  [&collect](const rvsdg::Node & node, util::HashSet<rvsdg::Node *> & dependentNodes)
165  {
166  for (auto & output : node.Outputs())
167  {
168  for (auto & user : output.Users())
169  {
170  if (const auto userNode = rvsdg::TryGetOwnerNode<rvsdg::Node>(user))
171  {
172  if (dependentNodes.insert(userNode))
173  {
174  collect(*userNode, dependentNodes);
175  }
176  }
177  }
178  }
179  };
180 
181  util::HashSet<rvsdg::Node *> dependentNodes;
182  collect(node, dependentNodes);
183  return dependentNodes;
184 }
185 
186 std::vector<rvsdg::Node *>
188 {
189  if (nodes.IsEmpty())
190  {
191  return {};
192  }
193 
194  std::vector<rvsdg::Node *> sortedNodes;
195  for (auto & node : nodes.Items())
196  {
197  sortedNodes.push_back(node);
198  }
199 
200  auto depthMap = rvsdg::computeDepthMap(*sortedNodes[0]->region());
201  std::sort(
202  sortedNodes.begin(),
203  sortedNodes.end(),
204  [&depthMap](const auto * node1, const auto * node2)
205  {
206  JLM_ASSERT(depthMap.find(node1) != depthMap.end());
207  JLM_ASSERT(depthMap.find(node2) != depthMap.end());
208  return depthMap[node1] < depthMap[node2];
209  });
210 
211  return sortedNodes;
212 }
213 
214 size_t
216 {
217  const auto dependentNodes = collectDependentNodes(gammaNode);
218  const auto sortedDependentNodes = sortByDepth(dependentNodes);
219 
220  // FIXME: We create too many entry and exit variables for the gamma node as we copy each
221  // node individually instead of the entire subgraph. Rather copy the entire subgraph at once and
222  // create entry and exit variables according to the "outer edges" of this subgraph.
223  for (auto & node : sortedDependentNodes)
224  {
225  // Collect operands for each subregion we copy the node into
226  std::unordered_map<rvsdg::Region *, std::vector<rvsdg::Output *>> subregionOperands;
227  for (auto & input : node->Inputs())
228  {
229  auto & oldOperand = *input.origin();
230  if (rvsdg::TryGetOwnerNode<rvsdg::Node>(oldOperand) == &gammaNode)
231  {
232  auto [branchResults, _] = gammaNode.MapOutputExitVar(oldOperand);
233  for (const auto branchResult : branchResults)
234  {
235  subregionOperands[branchResult->region()].push_back(branchResult->origin());
236  }
237  }
238  else
239  {
240  auto [_, branchArguments] = gammaNode.AddEntryVar(&oldOperand);
241  for (auto branchArgument : branchArguments)
242  {
243  subregionOperands[branchArgument->region()].push_back(branchArgument);
244  }
245  }
246  }
247 
248  // Copy node into each subregion and collect outputs of each copy
249  std::unordered_map<rvsdg::Region *, std::vector<rvsdg::Output *>> subregionOutputs;
250  for (auto & subregion : gammaNode.Subregions())
251  {
252  const auto copiedNode = node->copy(&subregion, subregionOperands.at(&subregion));
253  subregionOutputs[&subregion] = rvsdg::outputs(copiedNode);
254  }
255 
256  // Adjust outputs of original node
257  for (auto & output : node->Outputs())
258  {
259  std::vector<rvsdg::Output *> branchResultOperands;
260  for (auto & subregion : gammaNode.Subregions())
261  {
262  branchResultOperands.push_back(subregionOutputs[&subregion][output.index()]);
263  }
264 
265  auto [_, exitVarOutput] = gammaNode.AddExitVar(branchResultOperands);
266  output.divert_users(exitVarOutput);
267  }
268  }
269 
270  return dependentNodes.Size();
271 }
272 
273 static size_t
275 {
276  JLM_ASSERT(isSingleSuccessor(*node, *gamma));
277 
278  /* collect all gamma inputs */
279  std::unordered_set<const rvsdg::Input *> inputs;
280  for (size_t n = 0; n < node->noutputs(); n++)
281  {
282  for (const auto & user : node->output(n)->Users())
283  {
284  inputs.insert(&user);
285  }
286  }
287 
288  /* collect subregions where node is used */
289  std::unordered_set<rvsdg::Region *> subregions;
290  for (const auto & input : inputs)
291  {
292  std::visit(
293  [&subregions](const auto & rolevar)
294  {
295  if constexpr (std::is_same<std::decay_t<decltype(rolevar)>, rvsdg::GammaNode::EntryVar>())
296  {
297  for (const auto & argument : rolevar.branchArgument)
298  {
299  if (argument->nusers() != 0)
300  subregions.insert(argument->region());
301  }
302  }
303  else if constexpr (std::is_same<
304  std::decay_t<decltype(rolevar)>,
306  {
307  for (const auto & argument : rolevar.matchContent)
308  {
309  if (argument->nusers() != 0)
310  subregions.insert(argument->region());
311  }
312  }
313  else
314  {
315  JLM_UNREACHABLE("A gamma input must either be the match variable or an entry variable");
316  }
317  },
318  gamma->MapInput(*input));
319  }
320 
321  return subregions.size();
322 }
323 
324 void
326 {
327  /*
328  We don't want to pull anything into empty gammas with two subregions,
329  as they are translated to select instructions in the r2j phase.
330  */
331  if (gamma->nsubregions() == 2 && empty(gamma))
332  return;
333 
334  auto prednode = rvsdg::TryGetOwnerNode<rvsdg::Node>(*gamma->predicate()->origin());
335 
336  /* FIXME: This is inefficient. We can do better. */
337  auto evs = gamma->GetEntryVars();
338  size_t index = 0;
339  while (index < evs.size())
340  {
341  const auto & ev = evs[index];
342  auto node = rvsdg::TryGetOwnerNode<rvsdg::Node>(*ev.input->origin());
343  if (!node || prednode == node || !isSingleSuccessor(*node, *gamma))
344  {
345  index++;
346  continue;
347  }
348 
349  if (is_used_in_nsubregions(gamma, node) == 1)
350  {
351  /*
352  FIXME: This function pulls in the node to ALL subregions and
353  not just the one we care about.
354  */
355  pullin_node(gamma, node);
356  cleanup(gamma, node);
357  evs = gamma->GetEntryVars();
358  index = 0;
359  }
360  else
361  {
362  index++;
363  }
364  }
365 }
366 
367 void
369 {
370  for (auto & node : rvsdg::TopDownTraverser(region))
371  {
372  if (auto structnode = dynamic_cast<rvsdg::StructuralNode *>(node))
373  {
374  if (auto gamma = dynamic_cast<rvsdg::GammaNode *>(node))
375  pull(gamma);
376 
377  for (size_t n = 0; n < structnode->nsubregions(); n++)
378  pull(structnode->subregion(n));
379  }
380  }
381 }
382 
383 static void
384 pull(rvsdg::RvsdgModule & module, util::StatisticsCollector & statisticsCollector)
385 {
386  auto statistics = NodeSinking::Statistics::Create(module.SourceFilePath().value());
387 
388  statistics->start(module.Rvsdg());
389  pull(&module.Rvsdg().GetRootRegion());
390  statistics->end(module.Rvsdg());
391 
392  statisticsCollector.CollectDemandedStatistics(std::move(statistics));
393 }
394 
395 NodeSinking::~NodeSinking() noexcept = default;
396 
397 void
398 NodeSinking::Run(rvsdg::RvsdgModule & module, util::StatisticsCollector & statisticsCollector)
399 {
400  pull(module, statisticsCollector);
401 }
402 
403 }
void end(const rvsdg::Graph &graph) noexcept
Definition: pull.cpp:34
Statistics(const util::FilePath &sourceFile)
Definition: pull.cpp:22
static std::unique_ptr< Statistics > Create(const util::FilePath &sourceFile)
Definition: pull.cpp:41
void start(const rvsdg::Graph &graph) noexcept
Definition: pull.cpp:27
Node Sinking Optimization.
Definition: pull.hpp:24
static std::vector< rvsdg::Node * > sortByDepth(const util::HashSet< rvsdg::Node * > &nodes)
Definition: pull.cpp:187
static util::HashSet< rvsdg::Node * > collectDependentNodes(const rvsdg::Node &node)
Definition: pull.cpp:161
static size_t sinkDependentNodesIntoGamma(rvsdg::GammaNode &gammaNode)
Definition: pull.cpp:215
~NodeSinking() noexcept override
Conditional operator / pattern matching.
Definition: gamma.hpp:99
void RemoveEntryVars(const std::vector< EntryVar > &entryVars)
Removes the given entry variables.
Definition: gamma.cpp:420
ExitVar MapOutputExitVar(const rvsdg::Output &output) const
Maps gamma output to exit variable description.
Definition: gamma.cpp:377
std::variant< MatchVar, EntryVar > MapInput(const rvsdg::Input &input) const
Maps gamma input to its role (match variable or entry variable).
Definition: gamma.cpp:314
EntryVar AddEntryVar(rvsdg::Output *origin)
Routes a variable into the gamma branches.
Definition: gamma.cpp:260
std::vector< EntryVar > GetEntryVars() const
Gets all entry variables for this gamma.
Definition: gamma.cpp:303
ExitVar AddExitVar(std::vector< rvsdg::Output * > values)
Routes per-branch result of gamma to output.
Definition: gamma.cpp:342
rvsdg::Input * predicate() const noexcept
Definition: gamma.hpp:398
Region & GetRootRegion() const noexcept
Definition: graph.hpp:99
Output * origin() const noexcept
Definition: node.hpp:58
OutputIteratorRange Outputs() noexcept
Definition: node.hpp:657
NodeInput * input(size_t index) const noexcept
Definition: node.hpp:615
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
UsersRange Users()
Definition: node.hpp:354
Represent acyclic RVSDG subgraphs.
Definition: region.hpp:213
size_t numNodes() const noexcept
Definition: region.hpp:481
const std::optional< util::FilePath > & SourceFilePath() const noexcept
Definition: RvsdgModule.hpp:73
Graph & Rvsdg() noexcept
Definition: RvsdgModule.hpp:57
SubregionIteratorRange Subregions()
size_t nsubregions() const noexcept
rvsdg::Region * subregion(size_t index) const noexcept
IteratorRange< ItemConstIterator > Items() const noexcept
Definition: HashSet.hpp:223
bool IsEmpty() const noexcept
Definition: HashSet.hpp:198
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 AddMeasurement(std::string name, T value)
Definition: Statistics.hpp:174
void start() noexcept
Definition: time.hpp:54
void stop() noexcept
Definition: time.hpp:67
#define JLM_ASSERT(x)
Definition: common.hpp:16
#define JLM_UNREACHABLE(msg)
Definition: common.hpp:43
Global memory state passed between functions.
static void pullin_node(rvsdg::GammaNode *gamma, rvsdg::Node *node)
Definition: pull.cpp:86
static void cleanup(rvsdg::GammaNode *gamma, rvsdg::Node *node)
Definition: pull.cpp:116
static size_t is_used_in_nsubregions(const rvsdg::GammaNode *gamma, const rvsdg::Node *node)
Definition: pull.cpp:274
static bool empty(const rvsdg::GammaNode *gamma)
Definition: pull.cpp:48
void pullin_top(rvsdg::GammaNode *gamma)
Definition: pull.cpp:134
static bool isSingleSuccessor(const rvsdg::Node &node, const rvsdg::Node &singleSuccessor)
Definition: pull.cpp:70
void pull(rvsdg::GammaNode *gamma)
Definition: pull.cpp:325
static void remove(Node *node)
Definition: region.hpp:932
std::unordered_map< const Node *, size_t > computeDepthMap(const Region &region)
Definition: region.cpp:609
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:682
static std::vector< jlm::rvsdg::Output * > outputs(const Node *node)
Definition: node.hpp:1058
A variable routed into all gamma regions.
Definition: gamma.hpp:131
The match/discriminator variable of this gamma node.
Definition: gamma.hpp:116
static const char * Timer
Definition: Statistics.hpp:240
static const char * NumRvsdgInputsAfter
Definition: Statistics.hpp:215
static const char * NumRvsdgInputsBefore
Definition: Statistics.hpp:214