Jlm
Loading...
Searching...
No Matches
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>
13
14namespace 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
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
70PointsToGraph::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
101PointsToGraph::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
115PointsToGraph::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
153PointsToGraph::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
165PointsToGraph::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
204void
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
215void
217{
218 JLM_ASSERT(index < nodeData_.size());
220 {
222 nodeData_[index].isTargetingAllExternallyAvailable = true;
223 }
224}
225
226bool
228{
229 JLM_ASSERT(source < nodeExplicitTargets_.size());
230
231 // The external memory node should never be an explicit target
233 // The external memory node should never have explicit targets
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
245std::pair<size_t, size_t>
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
268std::string
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;
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
323bool
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
493void
494PointsToGraph::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 const void * object = pointsToGraph.nodeObjects_[ptgNode];
518 if (object)
519 node.SetAttributeObject("rvsdgObject", reinterpret_cast<uintptr_t>(object));
520 }
521 else
522 {
523 // Register nodes are oval
524 node.SetShape("oval");
525 }
526
527 nodes[ptgNode] = &node;
528 }
529
530 // Attach all rvsdg outputs to their RegisterNode, using incrementing attribute names
531 std::unordered_map<NodeIndex, size_t> outputCount;
532 for (const auto & [rvsdgOutput, ptgNode] : pointsToGraph.registerMap_)
533 {
534 const auto count = outputCount[ptgNode]++;
535 nodes[ptgNode]->SetAttributeObject(util::strfmt("output", count), *rvsdgOutput);
536 }
537
538 // Add all explicit edges
539 for (NodeIndex ptgNode = 0; ptgNode < pointsToGraph.numNodes(); ptgNode++)
540 {
541 for (auto target : pointsToGraph.getExplicitTargets(ptgNode).Items())
542 {
543 graph.CreateEdge(*nodes[ptgNode], *nodes[target], true);
544 }
545 }
546}
547
548std::string
550{
551 util::graph::Writer writer;
552 dumpGraph(writer, pointsToGraph);
553 std::ostringstream ss;
555 return ss.str();
556}
557
558}
std::vector< rvsdg::Node * > nodes
static rvsdg::Input & sizeInput(const rvsdg::Node &node)
RegisterNodeRange registerNodes() const noexcept
const rvsdg::SimpleNode & getMallocForNode(NodeIndex index) const
util::MapValueIterator< const NodeIndex, LambdaNodeMap::const_iterator > LambdaNodeIterator
static void dumpGraph(util::graph::Writer &graphWriter, const PointsToGraph &pointsToGraph)
size_t numNodes() const noexcept
NodeIndex getNodeForRegister(const rvsdg::Output &output) const
NodeIndex addNodeForMalloc(const rvsdg::SimpleNode &mallocNode, bool externallyAvailable)
const rvsdg::LambdaNode & getLambdaForNode(NodeIndex index) const
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::DeltaNode & getDeltaForNode(NodeIndex index) const
const rvsdg::SimpleNode & getAllocaForNode(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::optional< size_t > tryGetNodeSize(NodeIndex index) 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)
const util::HashSet< NodeIndex > & getExplicitTargets(NodeIndex index) const
NodeKind getNodeKind(NodeIndex index) const
bool isMemoryNode(NodeIndex index) const
bool hasNodeForRegister(const rvsdg::Output &output) 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
const rvsdg::GraphImport & getImportForNode(NodeIndex index) const
ImportNodeRange importNodes() const noexcept
std::vector< NodeIndex > externallyAvailableNodes_
const DeltaOperation & GetOperation() const noexcept override
Definition delta.cpp:71
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition delta.hpp:63
bool constant() const noexcept
Definition delta.hpp:51
std::string debug_string() const override
Definition graph.cpp:22
Output * origin() const noexcept
Definition node.hpp:58
const SimpleOperation & GetOperation() const noexcept override
std::string DebugString() const override
NodeInput * input(size_t index) const noexcept
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:70
const char *const Yellow
static std::string strfmt(Args... args)
Definition strfmt.hpp:35