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