Jlm
PointsToGraph.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2020 Nico Reißmann <nico.reissmann@gmail.com>
3  * Copyright 2025 Håvard Krogstie <krogstie.havard@gmail.com>
4  * See COPYING for terms of redistribution.
5  */
6 
9 #include <jlm/llvm/ir/Trace.hpp>
11 #include <jlm/util/GraphWriter.hpp>
12 
13 namespace jlm::llvm::aa
14 {
15 
17 {
18  // Create the single external node, representing all memory not represented by any other node
19  externalMemoryNode_ = addNode(NodeKind::ExternalNode, true, false, std::nullopt, nullptr);
20  // The external node can never be explicitly targeted and never explicitly target any other node
22 }
23 
26 {
27  return { AllocaNodeIterator(allocaMap_.cbegin()), AllocaNodeIterator(allocaMap_.cend()) };
28 }
29 
31 PointsToGraph::deltaNodes() const noexcept
32 {
33  return { DeltaNodeIterator(deltaMap_.cbegin()), DeltaNodeIterator(deltaMap_.cend()) };
34 }
35 
38 {
39  return { ImportNodeIterator(importMap_.cbegin()), ImportNodeIterator(importMap_.cend()) };
40 }
41 
44 {
45  return { LambdaNodeIterator(lambdaMap_.cbegin()), LambdaNodeIterator(lambdaMap_.cend()) };
46 }
47 
50 {
51  return { MallocNodeIterator(mallocMap_.cbegin()), MallocNodeIterator(mallocMap_.cend()) };
52 }
53 
56 {
57  return externalMemoryNode_;
58 }
59 
62 {
63  return { registerNodes_.cbegin(), registerNodes_.cend() };
64 }
65 
67 PointsToGraph::addNodeForAlloca(const rvsdg::SimpleNode & allocaNode, bool externallyAvailable)
68 {
69  if (!is<AllocaOperation>(allocaNode.GetOperation()))
70  throw std::logic_error("Node is not an alloca operation");
71 
72  auto [it, added] = allocaMap_.try_emplace(&allocaNode, 0);
73  if (!added)
74  throw std::logic_error("Alloca node already exists in the graph.");
75 
76  // Try to include the size of the allocation in the created node
77  const auto getMemorySize = [](const rvsdg::Node & allocaNode) -> std::optional<size_t>
78  {
79  const auto allocaOp = util::assertedCast<const AllocaOperation>(
80  &static_cast<const rvsdg::SimpleNode &>(allocaNode).GetOperation());
81 
82  // An alloca has a count parameter, which on rare occasions is not just the constant 1.
83  const auto elementCount = tryGetConstantSignedInteger(*allocaNode.input(0)->origin());
84  if (elementCount.has_value() && *elementCount >= 0)
85  return *elementCount * GetTypeAllocSize(*allocaOp->ValueType());
86  return std::nullopt;
87  };
88 
89  return it->second = addNode(
91  externallyAvailable,
92  false,
93  getMemorySize(allocaNode),
94  &allocaNode);
95 }
96 
98 PointsToGraph::addNodeForDelta(const rvsdg::DeltaNode & deltaNode, bool externallyAvailable)
99 {
100  auto [it, added] = deltaMap_.try_emplace(&deltaNode, 0);
101  if (!added)
102  throw std::logic_error("Delta node already exists in the graph.");
103 
104  const auto isConstant = deltaNode.GetOperation().constant();
105  const auto memorySize = GetTypeAllocSize(*deltaNode.GetOperation().Type());
106 
107  return it->second =
108  addNode(NodeKind::DeltaNode, externallyAvailable, isConstant, memorySize, &deltaNode);
109 }
110 
112 PointsToGraph::addNodeForImport(const rvsdg::GraphImport & import, bool externallyAvailable)
113 {
114  auto [it, added] = importMap_.try_emplace(&import, 0);
115  if (!added)
116  throw std::logic_error("Import node already exists in the graph.");
117 
118  const auto isConstant = [](const rvsdg::GraphImport & import) -> bool
119  {
120  if (const auto graphImport = dynamic_cast<const LlvmGraphImport *>(&import))
121  return graphImport->isConstant();
122  return false;
123  };
124 
125  const auto getMemorySize = [](const rvsdg::GraphImport & import) -> std::optional<size_t>
126  {
127  if (const auto graphImport = dynamic_cast<const LlvmGraphImport *>(&import))
128  {
129  auto size = GetTypeAllocSize(*graphImport->ValueType());
130 
131  // C code can contain declarations like this:
132  // extern char myArray[];
133  // which means there is an array of unknown size defined in a different module.
134  // In the LLVM IR the import gets an array length of 0, but that is not correct.
135  if (size != 0)
136  return size;
137  }
138  return std::nullopt;
139  };
140 
141  return it->second = addNode(
143  externallyAvailable,
144  isConstant(import),
145  getMemorySize(import),
146  &import);
147 }
148 
150 PointsToGraph::addNodeForLambda(const rvsdg::LambdaNode & lambdaNode, bool externallyAvailable)
151 {
152  auto [it, added] = lambdaMap_.try_emplace(&lambdaNode, 0);
153  if (!added)
154  throw std::logic_error("Lambda node already exists in the graph.");
155 
156  // A function can never be written to, so it is regarded constant
157  // It should never be read from either, so its size is 0
158  return it->second = addNode(NodeKind::LambdaNode, externallyAvailable, true, 0, &lambdaNode);
159 }
160 
162 PointsToGraph::addNodeForMalloc(const rvsdg::SimpleNode & mallocNode, bool externallyAvailable)
163 {
164  if (!is<MallocOperation>(mallocNode.GetOperation()))
165  throw std::logic_error("Node is not an alloca operation");
166 
167  auto [it, added] = mallocMap_.try_emplace(&mallocNode, 0);
168  if (!added)
169  throw std::logic_error("Malloc node already exists in the graph.");
170 
171  const auto tryGetMemorySize = [](const rvsdg::Node & mallocNode) -> std::optional<size_t>
172  {
173  // If the size parameter of the malloc node is a constant, that is our size
174  auto size = tryGetConstantSignedInteger(*MallocOperation::sizeInput(mallocNode).origin());
175 
176  // Only return the size if it is a positive integer, to avoid unsigned underflow
177  if (size.has_value() && *size >= 0)
178  return *size;
179 
180  return std::nullopt;
181  };
182 
183  return it->second = addNode(
185  externallyAvailable,
186  false,
187  tryGetMemorySize(mallocNode),
188  &mallocNode);
189 }
190 
193 {
194  // Registers need to be static single assignment, so they are regarded as constants
195  // Their size in memory is not known, as most registers never even live in memory.
196  auto index = addNode(NodeKind::RegisterNode, false, true, std::nullopt, nullptr);
197  registerNodes_.emplace_back(index);
198  return index;
199 }
200 
201 void
203 {
204  if (!isRegisterNode(nodeIndex))
205  throw std::logic_error("Node is not a register node");
206 
207  const auto [_, added] = registerMap_.emplace(&output, nodeIndex);
208  if (!added)
209  throw std::logic_error("Register is already mapped in the graph.");
210 }
211 
212 void
214 {
215  JLM_ASSERT(index < nodeData_.size());
217  {
219  nodeData_[index].isTargetingAllExternallyAvailable = true;
220  }
221 }
222 
223 bool
225 {
226  JLM_ASSERT(source < nodeExplicitTargets_.size());
227 
228  // The external memory node should never be an explicit target
229  JLM_ASSERT(target != externalMemoryNode_);
230  // The external memory node should never have explicit targets
231  JLM_ASSERT(source != externalMemoryNode_);
232 
233  // Register nodes can never be targeted
235 
236  // Skip adding the target if it is already being targeted implicitly
238  return false;
239  return nodeExplicitTargets_[source].insert(target);
240 }
241 
242 std::pair<size_t, size_t>
243 PointsToGraph::numEdges() const noexcept
244 {
245  size_t numExplicitEdges = 0;
246  size_t numImplicitEdges = 0;
247  size_t numDoubledUpEdges = 0;
248 
249  for (NodeIndex i = 0; i < numNodes(); i++)
250  {
251  for (auto target : getExplicitTargets(i).Items())
252  {
253  numExplicitEdges++;
255  numDoubledUpEdges++;
256  }
257 
259  numImplicitEdges += externallyAvailableNodes_.size();
260  }
261 
262  return std::make_pair(numExplicitEdges, numExplicitEdges + numImplicitEdges - numDoubledUpEdges);
263 }
264 
265 std::string
266 PointsToGraph::getNodeDebugString(NodeIndex index, char seperator) const
267 {
268  std::ostringstream ss;
269  ss << "(PtG#" << std::setfill('0') << std::setw(3) << index << std::setw(0) << " ";
270 
271  switch (getNodeKind(index))
272  {
274  ss << getAllocaForNode(index).DebugString();
275  break;
276  case NodeKind::DeltaNode:
277  ss << getDeltaForNode(index).DebugString();
278  break;
280  ss << getImportForNode(index).debug_string();
281  break;
283  ss << getLambdaForNode(index).DebugString();
284  break;
286  ss << getMallocForNode(index).DebugString();
287  break;
289  ss << "external";
290  break;
292  ss << "register";
293  break;
294  default:
295  throw std::logic_error("Unknown PtG node kind");
296  }
297 
298  if (isExternallyAvailable(index))
299  {
300  ss << seperator << "(ExtAv)";
301  }
303  {
304  ss << seperator << "(TgtAllExtAv)";
305  }
306 
307  if (const auto size = tryGetNodeSize(index))
308  {
309  ss << seperator << "(" << size.value() << " bytes)";
310  }
311  if (isNodeConstant(index))
312  {
313  ss << seperator << "(const)";
314  }
315  ss << " )";
316 
317  return ss.str();
318 }
319 
320 bool
322 {
323  // Given a memory node in the PointsToGraph called "original",
324  // finds the corresponding memory node in the PointsToGraph called "other".
325  // If no corresponding memory node exists in the graph, nullopt is returned.
326  auto getCorrespondingMemoryNode = [](NodeIndex node,
327  const PointsToGraph & original,
328  const PointsToGraph & other) -> std::optional<NodeIndex>
329  {
330  const auto kind = original.getNodeKind(node);
331  if (kind == NodeKind::AllocaNode)
332  {
333  const auto & alloca = original.getAllocaForNode(node);
334  if (!other.hasNodeForAlloca(alloca))
335  return std::nullopt;
336  return other.getNodeForAlloca(alloca);
337  }
338  if (kind == NodeKind::DeltaNode)
339  {
340  const auto & delta = original.getDeltaForNode(node);
341  if (!other.hasNodeForDelta(delta))
342  return std::nullopt;
343  return other.getNodeForDelta(delta);
344  }
345  if (kind == NodeKind::ImportNode)
346  {
347  const auto & argument = original.getImportForNode(node);
348  if (!other.hasNodeForImport(argument))
349  return std::nullopt;
350  return other.getNodeForImport(argument);
351  }
352  if (kind == NodeKind::LambdaNode)
353  {
354  const auto & lambda = original.getLambdaForNode(node);
355  if (!other.hasNodeForLambda(lambda))
356  return std::nullopt;
357  return other.getNodeForLambda(lambda);
358  }
359  if (kind == NodeKind::MallocNode)
360  {
361  const auto & malloc = original.getMallocForNode(node);
362  if (!other.hasNodeForMalloc(malloc))
363  return std::nullopt;
364  return other.getNodeForMalloc(malloc);
365  }
366  if (kind == NodeKind::ExternalNode)
367  {
368  return other.getExternalMemoryNode();
369  }
370 
371  throw std::logic_error("Unknown type of memory node");
372  };
373 
374  // Given two nodes, checks if the superset node points to everything the subset node points to,
375  // and that the superset node has all flags the subset node has
376  auto isNodeSuperset = [&](const PointsToGraph & supersetGraph,
377  NodeIndex supersetNode,
378  const PointsToGraph & subsetGraph,
379  NodeIndex subsetNode)
380  {
381  // The superset node must have any flags set on the subset node
382  if (subsetGraph.isExternallyAvailable(subsetNode)
383  && !supersetGraph.isExternallyAvailable(supersetNode))
384  return false;
385 
386  if (subsetGraph.isTargetingAllExternallyAvailable(subsetNode)
387  && !supersetGraph.isTargetingAllExternallyAvailable(supersetNode))
388  return false;
389 
390  // Make sure all targets of the subset node are also targets of the superset node
391  for (auto subsetTarget : subsetGraph.getExplicitTargets(subsetNode).Items())
392  {
393  // There must be a corresponding memory node representing the target in the superset graph
394  auto correspondingTarget =
395  getCorrespondingMemoryNode(subsetTarget, subsetGraph, supersetGraph);
396  if (!correspondingTarget.has_value())
397  return false;
398 
399  // If the target is a member of the explicit targets of the superset node, it is OK
400  if (supersetGraph.getExplicitTargets(supersetNode).Contains(*correspondingTarget))
401  continue;
402 
403  // The target can also be an implicit target of the superset node
404  if (supersetGraph.isTargetingAllExternallyAvailable(supersetNode)
405  && supersetGraph.isExternallyAvailable(*correspondingTarget))
406  continue;
407 
408  // If we get here, the subset node has a target that the superset node does not
409  return false;
410  }
411 
412  return true;
413  };
414 
415  // Given a memory node from the subgraph, check that a corresponding node exists in this graph.
416  // All edges the other node has, must have corresponding edges in this graph.
417  // If the other node is marked as escaping, the corresponding node must be as well.
418  auto hasSuperOfMemoryNode = [&](NodeIndex subsetNode)
419  {
420  auto thisNode = getCorrespondingMemoryNode(subsetNode, subgraph, *this);
421  if (!thisNode.has_value())
422  return false;
423 
424  return isNodeSuperset(*this, *thisNode, subgraph, subsetNode);
425  };
426 
427  // Iterate through all memory nodes in the subgraph, and make sure we have corresponding nodes
428  for (const auto node : subgraph.allocaNodes())
429  {
430  if (!hasSuperOfMemoryNode(node))
431  return false;
432  }
433  for (const auto node : subgraph.deltaNodes())
434  {
435  if (!hasSuperOfMemoryNode(node))
436  return false;
437  }
438  for (const auto node : subgraph.importNodes())
439  {
440  if (!hasSuperOfMemoryNode(node))
441  return false;
442  }
443  for (const auto node : subgraph.lambdaNodes())
444  {
445  if (!hasSuperOfMemoryNode(node))
446  return false;
447  }
448  for (const auto node : subgraph.mallocNodes())
449  {
450  if (!hasSuperOfMemoryNode(node))
451  return false;
452  }
453 
454  // For each register mapped to a RegisterNode in the subgraph, this graph must also have mapped
455  // the same register to be a supergraph. The RegisterNode must point to a superset of what
456  // the subgraph's RegisterNode points to.
457  for (auto [rvsdgOutput, subNode] : subgraph.registerMap_)
458  {
459  if (!hasNodeForRegister(*rvsdgOutput))
460  return false;
461  const auto supersetRegisterNode = getNodeForRegister(*rvsdgOutput);
462 
463  if (!isNodeSuperset(*this, supersetRegisterNode, subgraph, subNode))
464  return false;
465  }
466 
467  return true;
468 }
469 
472  NodeKind kind,
473  bool externallyAvailable,
474  bool isConstant,
475  std::optional<size_t> memorySize,
476  const void * object)
477 {
478  const auto index = nodeData_.size();
479 
480  nodeData_.emplace_back(NodeData(kind, externallyAvailable, false, isConstant, memorySize));
481  nodeExplicitTargets_.emplace_back();
482  nodeObjects_.push_back(object);
483 
484  if (externallyAvailable)
485  externallyAvailableNodes_.push_back(index);
486 
487  return index;
488 }
489 
490 void
491 PointsToGraph::dumpGraph(util::graph::Writer & graphWriter, const PointsToGraph & pointsToGraph)
492 {
493  const auto [explicitEdges, totalEdges] = pointsToGraph.numEdges();
494 
495  auto & graph = graphWriter.CreateGraph();
496  graph.SetLabel("Points-to graph");
497  graph.AppendToLabel(
498  util::strfmt("Explicit edges: ", explicitEdges, ", total edges: ", totalEdges));
499 
500  std::vector<util::graph::Node *> nodes;
501  nodes.resize(pointsToGraph.numNodes());
502 
503  for (NodeIndex ptgNode = 0; ptgNode < pointsToGraph.numNodes(); ptgNode++)
504  {
505  auto & node = graph.CreateNode();
506  node.SetLabel(pointsToGraph.getNodeDebugString(ptgNode, '\n'));
507  if (pointsToGraph.isExternallyAvailable(ptgNode))
508  node.SetFillColor(util::graph::Colors::Yellow);
509 
510  if (pointsToGraph.isMemoryNode(ptgNode))
511  {
512  // Memory nodes are boxes, and have an associated object
513  node.SetShape("box");
514  node.SetAttributeObject("rvsdgObject", pointsToGraph.nodeObjects_[ptgNode]);
515  }
516  else
517  {
518  // Register nodes are oval
519  node.SetShape("oval");
520  }
521 
522  nodes[ptgNode] = &node;
523  }
524 
525  // Attach all rvsdg outputs to their RegisterNode, using incrementing attribute names
526  std::unordered_map<NodeIndex, size_t> outputCount;
527  for (const auto & [rvsdgOutput, ptgNode] : pointsToGraph.registerMap_)
528  {
529  const auto count = outputCount[ptgNode]++;
530  nodes[ptgNode]->SetAttributeObject(util::strfmt("output", count), rvsdgOutput);
531  }
532 
533  // Add all explicit edges
534  for (NodeIndex ptgNode = 0; ptgNode < pointsToGraph.numNodes(); ptgNode++)
535  {
536  for (auto target : pointsToGraph.getExplicitTargets(ptgNode).Items())
537  {
538  graph.CreateEdge(*nodes[ptgNode], *nodes[target], true);
539  }
540  }
541 }
542 
543 std::string
544 PointsToGraph::dumpDot(const PointsToGraph & pointsToGraph)
545 {
546  util::graph::Writer writer;
547  dumpGraph(writer, pointsToGraph);
548  std::ostringstream ss;
550  return ss.str();
551 }
552 
553 }
static rvsdg::Input & sizeInput(const rvsdg::Node &node)
Definition: operators.hpp:2436
std::optional< size_t > tryGetNodeSize(NodeIndex index) const noexcept
RegisterNodeRange registerNodes() const noexcept
util::MapValueIterator< const NodeIndex, LambdaNodeMap::const_iterator > LambdaNodeIterator
const rvsdg::SimpleNode & getMallocForNode(NodeIndex index) const
static void dumpGraph(util::graph::Writer &graphWriter, const PointsToGraph &pointsToGraph)
size_t numNodes() const noexcept
const rvsdg::SimpleNode & getAllocaForNode(NodeIndex index) const
const rvsdg::GraphImport & getImportForNode(NodeIndex index) const
const util::HashSet< NodeIndex > & getExplicitTargets(NodeIndex index) const
NodeIndex getNodeForRegister(const rvsdg::Output &output) const
NodeIndex addNodeForMalloc(const rvsdg::SimpleNode &mallocNode, bool externallyAvailable)
util::MapValueIterator< const NodeIndex, DeltaNodeMap::const_iterator > DeltaNodeIterator
bool isExternallyAvailable(NodeIndex index) const
NodeIndex addNodeForImport(const rvsdg::GraphImport &argument, bool externallyAvailable)
std::string getNodeDebugString(NodeIndex index, char separator=' ') const
NodeIndex addNode(NodeKind kind, bool externallyAvailable, bool isConstant, std::optional< size_t > memorySize, const void *object)
AllocaNodeRange allocaNodes() const noexcept
bool isTargetingAllExternallyAvailable(NodeIndex index) const
std::vector< NodeData > nodeData_
NodeIndex getExternalMemoryNode() const noexcept
const rvsdg::LambdaNode & getLambdaForNode(NodeIndex index) const
NodeIndex addNodeForAlloca(const rvsdg::SimpleNode &allocaNode, bool externallyAvailable)
void mapRegisterToNode(const rvsdg::Output &output, NodeIndex nodeIndex)
NodeIndex addNodeForLambda(const rvsdg::LambdaNode &lambdaNode, bool externallyAvailable)
NodeIndex addNodeForDelta(const rvsdg::DeltaNode &deltaNode, bool externallyAvailable)
bool isSupergraphOf(const PointsToGraph &subgraph) const
bool isNodeConstant(NodeIndex index) const noexcept
LambdaNodeRange lambdaNodes() const noexcept
std::vector< const void * > nodeObjects_
DeltaNodeRange deltaNodes() const noexcept
bool addTarget(NodeIndex source, NodeIndex target)
void markAsTargetsAllExternallyAvailable(NodeIndex index)
util::MapValueIterator< const NodeIndex, AllocaNodeMap::const_iterator > AllocaNodeIterator
bool isRegisterNode(NodeIndex index) const
std::vector< NodeIndex > registerNodes_
static std::string dumpDot(const PointsToGraph &pointsToGraph)
NodeKind getNodeKind(NodeIndex index) const
bool isMemoryNode(NodeIndex index) const
bool hasNodeForRegister(const rvsdg::Output &output) const
const rvsdg::DeltaNode & getDeltaForNode(NodeIndex index) const
std::vector< util::HashSet< NodeIndex > > nodeExplicitTargets_
std::pair< size_t, size_t > numEdges() const noexcept
util::MapValueIterator< const NodeIndex, MallocNodeMap::const_iterator > MallocNodeIterator
MallocNodeRange mallocNodes() const noexcept
util::MapValueIterator< const NodeIndex, ImportNodeMap::const_iterator > ImportNodeIterator
ImportNodeRange importNodes() const noexcept
std::vector< NodeIndex > externallyAvailableNodes_
Delta node.
Definition: delta.hpp:129
const DeltaOperation & GetOperation() const noexcept override
Definition: delta.cpp:71
bool constant() const noexcept
Definition: delta.hpp:51
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition: delta.hpp:63
std::string debug_string() const override
Definition: graph.cpp:22
Output * origin() const noexcept
Definition: node.hpp:58
Lambda node.
Definition: lambda.hpp:83
const SimpleOperation & GetOperation() const noexcept override
Definition: simple-node.cpp:48
std::string DebugString() const override
Definition: simple-node.cpp:79
NodeInput * input(size_t index) const noexcept
Definition: simple-node.hpp:82
std::string DebugString() const override
void SetLabel(std::string label)
void outputAllGraphs(std::ostream &out, OutputFormat format)
#define JLM_ASSERT(x)
Definition: common.hpp:16
size_t GetTypeAllocSize(const rvsdg::Type &type)
Definition: types.cpp:473
std::optional< int64_t > tryGetConstantSignedInteger(const rvsdg::Output &output)
Definition: Trace.cpp:46
const char *const Yellow
static std::string strfmt(Args... args)
Definition: strfmt.hpp:35