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