Jlm
Loading...
Searching...
No Matches
InvariantValueRedirection.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
14#include <jlm/llvm/ir/Trace.hpp>
18#include <jlm/rvsdg/gamma.hpp>
20#include <jlm/rvsdg/theta.hpp>
23
24namespace jlm::llvm
25{
26
28{
29public:
30 ~Statistics() override = default;
31
32 explicit Statistics(const util::FilePath & sourceFile)
34 {}
35
36 void
37 Start() noexcept
38 {
39 AddTimer(Label::Timer).start();
40 }
41
42 void
43 Stop() noexcept
44 {
45 GetTimer(Label::Timer).stop();
46 }
47
48 static std::unique_ptr<Statistics>
49 Create(const util::FilePath & sourceFile)
50 {
51 return std::make_unique<Statistics>(sourceFile);
52 }
53};
54
56
57void
59 rvsdg::RvsdgModule & module,
61{
62 auto statistics = Statistics::Create(module.SourceFilePath().value());
63
64 statistics->Start();
66 statistics->Stop();
67
69}
70
71void
73 rvsdg::RvsdgModule & rvsdgModule,
74 Configuration configuration)
75{
77 InvariantValueRedirection invariantValueRedirection(std::move(configuration));
78 invariantValueRedirection.Run(rvsdgModule, statisticsCollector);
79}
80
81void
83{
84 // We require a topdown traversal in the root region to ensure that a lambda node is visited
85 // before its call nodes. This ensures that all invariant values are redirected in the lambda
86 // subregion before we try to detect invariant call outputs.
87 for (auto node : rvsdg::TopDownTraverser(&rvsdg.GetRootRegion()))
88 {
89 MatchTypeOrFail(
90 *node,
91 [this](const rvsdg::LambdaNode & lambdaNode)
92 {
93 redirectInRegion(*lambdaNode.subregion());
94 },
95 [this](const rvsdg::PhiNode & phiNode)
96 {
97 auto phiLambdaNodes = rvsdg::PhiNode::ExtractLambdaNodes(phiNode);
98 for (auto phiLambdaNode : phiLambdaNodes)
99 {
100 redirectInRegion(*phiLambdaNode->subregion());
101 }
102 },
103 [](const rvsdg::DeltaNode &)
104 {
105 // Nothing needs to be done.
106 // Delta nodes are irrelevant for invariant value redirection.
107 },
108 [](const rvsdg::SimpleNode & simpleNode)
109 {
110 MatchTypeOrFail(
111 simpleNode.GetOperation(),
113 {
114 // Nothing needs to be done.
115 },
117 {
118 // Nothing needs to be done.
119 });
120 });
121 }
122}
123
124void
126{
127 const auto isGammaNode = !!dynamic_cast<rvsdg::GammaNode *>(region.node());
128 const auto isThetaNode = !!dynamic_cast<rvsdg::ThetaNode *>(region.node());
129 const auto isLambdaNode = !!dynamic_cast<rvsdg::LambdaNode *>(region.node());
130 JLM_ASSERT(isGammaNode || isThetaNode || isLambdaNode);
131
132 // We do not need a traverser here and can just iterate through all the nodes of a region as
133 // it is irrelevant in which order we handle the nodes.
134 for (auto & node : region.Nodes())
135 {
137 node,
138 [this](rvsdg::GammaNode & gammaNode)
139 {
140 // Ensure we redirect invariant values of all nodes in the gamma subregions first,
141 // otherwise we might not be able to redirect some of the gamma outputs.
142 redirectInSubregions(gammaNode);
143
145 redirectGammaOutputs(gammaNode);
146
149 },
150 [this](rvsdg::ThetaNode & thetaNode)
151 {
152 // Ensure we redirect invariant values of all nodes in the theta subregion first,
153 // otherwise we might not be able to redirect some of the theta outputs.
154 redirectInSubregions(thetaNode);
155
157 redirectThetaGammaOutputs(thetaNode);
158
160 redirectThetaOutputs(thetaNode);
161 },
162 [this](rvsdg::SimpleNode & simpleNode)
163 {
165 simpleNode.GetOperation(),
166 [this, &simpleNode](const CallOperation &)
167 {
168 if (configuration_.enableCallOutputRedirection)
169 redirectCallOutputs(simpleNode);
170 },
171 [this, &simpleNode](const LoadOperation &)
172 {
173 if (configuration_.enableLoadMemoryStateRedirection)
174 redirectLoadMemoryStates(simpleNode);
175 });
176 });
177 }
178
179 region.prune(false);
180}
181
182void
184{
185 const auto isGammaNode = !!dynamic_cast<rvsdg::GammaNode *>(&structuralNode);
186 const auto isThetaNode = !!dynamic_cast<rvsdg::ThetaNode *>(&structuralNode);
187 JLM_ASSERT(isGammaNode || isThetaNode);
188
189 for (auto & subregion : structuralNode.Subregions())
190 {
191 redirectInRegion(subregion);
192 }
193}
194
195void
197{
198 for (auto exitVar : gammaNode.GetExitVars())
199 {
200 if (auto invariantOrigin = rvsdg::GetGammaInvariantOrigin(gammaNode, exitVar))
201 {
202 exitVar.output->divert_users(*invariantOrigin);
203 }
204 }
205}
206
207void
209{
210 for (auto [branchResult, gammaOutput] : gammaNode.GetExitVars())
211 {
212 if (!rvsdg::is<rvsdg::ControlType>(gammaOutput->Type())
213 && !rvsdg::is<rvsdg::BitType>(gammaOutput->Type()))
214 continue;
215
216 std::optional<IntegerValueRepresentation> intValueOpt;
217 std::optional<rvsdg::ControlValueRepresentation> ctlValueOpt;
218 for (const auto result : branchResult)
219 {
220 auto & tracedOutput = llvm::traceOutput(*result->origin(), false);
221 if (const auto simpleNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(tracedOutput))
222 {
223 const bool done = rvsdg::MatchTypeWithDefault(
224 simpleNode->GetOperation(),
225 [&ctlValueOpt](const rvsdg::ControlConstantOperation & ctlConstantOp)
226 {
227 if (!ctlValueOpt.has_value())
228 {
229 ctlValueOpt = ctlConstantOp.value();
230 return false;
231 }
232
233 if (ctlValueOpt != ctlConstantOp.value())
234 {
235 return true;
236 }
237
238 // Nothing needs to be done
239 JLM_ASSERT(ctlValueOpt == ctlConstantOp.value());
240 return false;
241 },
242 [&intValueOpt](const IntegerConstantOperation & intConstantOp)
243 {
244 if (!intValueOpt.has_value())
245 {
246 intValueOpt = intConstantOp.Representation();
247 return false;
248 }
249
250 if (intValueOpt != intConstantOp.Representation())
251 {
252 return true;
253 }
254
255 // Nothing needs to be done
256 JLM_ASSERT(intValueOpt == intConstantOp.Representation());
257 return false;
258 },
259 [](const UndefValueOperation &)
260 {
261 // Nothing needs to be done
262 // A UndefValue can be a placeholder for any ControlConstantOperation
263 return false;
264 },
265 []()
266 {
267 // Any other operation means the transformation cannot be performed
268 return true;
269 });
270
271 if (done)
272 {
273 // We found a traced output that we could not deal with. Stop the transformation.
274 ctlValueOpt = std::nullopt;
275 intValueOpt = std::nullopt;
276 break;
277 }
278 }
279 else
280 {
281 // We found a traced output that we could not deal with. Stop the transformation.
282 ctlValueOpt = std::nullopt;
283 intValueOpt = std::nullopt;
284 break;
285 }
286 }
287
288 // At this point we know that the gamma exit variable could only be traced to a single
289 // rvsdg::ControlValueRepresentation
290 if (ctlValueOpt.has_value())
291 {
292 auto & ctlConstantOutput =
293 rvsdg::ControlConstantOperation::create(*gammaNode.region(), ctlValueOpt.value());
294 gammaOutput->divert_users(&ctlConstantOutput);
295 }
296
297 // At this point we know that the gamma exit variable could only be traced to a single
298 // IntegerValueRepresentation.
299 if (intValueOpt.has_value())
300 {
301 auto & intConstantNode =
302 IntegerConstantOperation::Create(*gammaNode.region(), intValueOpt.value());
303 gammaOutput->divert_users(intConstantNode.output(0));
304 }
305 }
306}
307
308static rvsdg::Node *
310{
311 const auto owner = output.GetOwner();
312 const auto ownerNode = std::get_if<rvsdg::Node *>(&owner);
313 if (!ownerNode)
314 return nullptr;
315
318 || rvsdg::is<UndefValueOperation>(*ownerNode))
319 return *ownerNode;
320
321 return nullptr;
322}
323
324void
325InvariantValueRedirection::redirectThetaOutputs(rvsdg::ThetaNode & thetaNode)
326{
327 for (const auto & loopVar : thetaNode.GetLoopVars())
328 {
329 // FIXME: In order to also redirect I/O state type variables, we need to know whether a loop
330 // terminates.
331 if (rvsdg::is<IOStateType>(loopVar.input->Type()))
332 continue;
333
335 {
336 loopVar.output->divert_users(loopVar.input->origin());
337 }
338 else if (const auto constantNode = getConstant(*loopVar.post->origin()))
339 {
340 auto copiedConstantNode = constantNode->copy(thetaNode.region(), {});
341 loopVar.output->divert_users(copiedConstantNode->output(0));
342 }
343 }
344}
345
346void
347InvariantValueRedirection::redirectThetaGammaOutputs(rvsdg::ThetaNode & thetaNode)
348{
349 auto correlationOpt = computeThetaGammaPredicateCorrelation(thetaNode);
350 if (!correlationOpt.has_value())
351 {
352 return;
353 }
354 auto & correlation = correlationOpt.value();
355
356 auto subregionRolesOpt = determineGammaSubregionRoles(*correlation);
357 if (!subregionRolesOpt.has_value())
358 {
359 // We could not determine the roles of the gamma subregions. Nothing can be done.
360 return;
361 }
362 auto roles = *subregionRolesOpt;
363 auto & gammaNode = correlation->gammaNode();
364
365 auto divertLoopVar =
366 [&gammaNode](rvsdg::ThetaNode::LoopVar & loopVar, rvsdg::Output & entryVarArgument)
367 {
369 {
370 auto roleVar = gammaNode.MapBranchArgument(entryVarArgument);
371 if (auto entryVar = std::get_if<rvsdg::GammaNode::EntryVar>(&roleVar))
372 {
373 loopVar.post->divert_to(entryVar->input->origin());
374 }
375 }
376 };
377
378 // At this point we can try to redirect the theta node loop variables
379 for (auto & loopVar : thetaNode.GetLoopVars())
380 {
381 if (loopVar.output->IsDead() && loopVar.pre->IsDead())
382 {
383 // The loop variable is completely dead. We do not need to waste any effort on it.
384 continue;
385 }
386
387 auto & loopVarPostOperand = *loopVar.post->origin();
388 if (rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(loopVarPostOperand) != &gammaNode)
389 {
390 // The post value of the loop variable does not originate from the gamma node. Nothing can
391 // be done.
392 continue;
393 }
394 auto [branchResult, _] = gammaNode.MapOutputExitVar(loopVarPostOperand);
395
396 if (loopVar.output->IsDead())
397 {
398 // The loop variables' output is dead, which means only its repetition value is of interest.
399 auto & entryVarArgument = *branchResult[roles.repetitionSubregion->index()]->origin();
400 divertLoopVar(loopVar, entryVarArgument);
401 }
402 else if (loopVar.pre->IsDead())
403 {
404 // The loop variables' pre value is dead, which means only its exit value is of interest.
405 auto & entryVarArgument = *branchResult[roles.exitSubregion->index()]->origin();
406 divertLoopVar(loopVar, entryVarArgument);
407 }
408 }
409}
410
411void
412InvariantValueRedirection::redirectCallOutputs(rvsdg::SimpleNode & callNode)
413{
414 JLM_ASSERT(is<CallOperation>(&callNode));
415
416 auto callTypeClassifier = CallOperation::ClassifyCall(callNode);
417 auto callType = callTypeClassifier->GetCallType();
418
419 // FIXME: We currently only support non-recursive direct calls. We would also like to get this
420 // working for recursive direct calls, but that requires a little bit more work as we need to be
421 // able to break the cycles between the recursive calls.
422 if (callType != CallTypeClassifier::CallType::NonRecursiveDirectCall)
423 return;
424
425 auto & lambdaNode =
426 rvsdg::AssertGetOwnerNode<rvsdg::LambdaNode>(callTypeClassifier->GetLambdaOutput());
427
428 // LLVM permits code where it can happen that the number and type of arguments handed in to the
429 // call node do not agree with the number and type of lambda parameters, even though it is a
430 // direct call. See jlm::tests::LambdaCallArgumentMismatch for an example. In this case, we cannot
431 // redirect the call outputs to the call operand as the types would not align, resulting in type
432 // errors.
433 if (CallOperation::NumArguments(callNode) != lambdaNode.GetFunctionArguments().size())
434 return;
435
436 // First, handle all call outputs where the corresponding function result is invariant
437 const auto results = lambdaNode.GetFunctionResults();
438 JLM_ASSERT(callNode.noutputs() == results.size());
439 for (size_t n = 0; n < callNode.noutputs(); n++)
440 {
441 const auto callOutput = callNode.output(n);
442
443 auto & lambdaResult = *results[n];
444 auto origin = lambdaResult.origin();
445 if (rvsdg::TryGetRegionParentNode<rvsdg::LambdaNode>(*origin) == &lambdaNode)
446 {
447 if (auto ctxvar = lambdaNode.MapBinderContextVar(*origin))
448 {
449 // This is a bound context variable.
450 // FIXME: We would like to get this case working as well, but we need to route the origin
451 // of the respective lambda input to the subregion of the call node.
452 }
453 else
454 {
455 auto callOperand = CallOperation::Argument(callNode, origin->index())->origin();
456 callOutput->divert_users(callOperand);
457 }
458 }
459 }
460
461 // Next, handle lambda bodies that contain memory state split and merge nodes.
462 // Memory state edges can only be routed around the call if the corresponding memory node id
463 // is invariant between the LambdaEntrySplit and the LambdaExitMerge.
464 const auto callExitSplit = CallOperation::tryGetMemoryStateExitSplit(callNode);
465 const auto callEntryMerge = CallOperation::tryGetMemoryStateEntryMerge(callNode);
466 const auto lambdaEntrySplit = tryGetMemoryStateEntrySplit(lambdaNode);
467 const auto lambdaExitMerge = tryGetMemoryStateExitMerge(lambdaNode);
468
469 // Only continue if the call / lambda pair has all four memory state nodes
470 if (callExitSplit == nullptr || callEntryMerge == nullptr || lambdaEntrySplit == nullptr
471 || lambdaExitMerge == nullptr)
472 return;
473
474 const auto callExitSplitOp =
475 *util::assertedCast<const CallExitMemoryStateSplitOperation>(&callExitSplit->GetOperation());
476 for (const auto memoryNodeId : callExitSplitOp.getMemoryNodeIds())
477 {
478 // TODO: This function contains special handling of the external memory node due to
479 // the possiblity of some memory nodes being compressed into the external memory node,
480 // with different functions compressing different sets of memory nodes.
481 // The merge and split nodes should ideally contain explicit information about compression,
482 // to avoid any loss of precision from assuming that missing memory nodes are still present.
483
484 // The memory state edge representing external in the caller can not be re-routed,
485 // because it may represent several memory state edges in the callee.
486 if (memoryNodeId == aa::PointsToGraph::externalMemoryNode)
487 continue;
488
489 // First try to find memory state edges corresponding directly
490 auto result = LambdaExitMemoryStateMergeOperation::tryMapMemoryNodeIdToInput(
491 *lambdaExitMerge,
492 memoryNodeId);
493 auto argument = LambdaEntryMemoryStateSplitOperation::tryMapMemoryNodeIdToOutput(
494 *lambdaEntrySplit,
495 memoryNodeId);
496
497 // If the memory node is not represented by a distinct memory state edge in the callee,
498 // assume that it is instead represented by the external memory node state edge.
499 if (result == nullptr)
500 result = LambdaExitMemoryStateMergeOperation::tryMapMemoryNodeIdToInput(
501 *lambdaExitMerge,
502 aa::PointsToGraph::externalMemoryNode);
503 if (argument == nullptr)
504 argument = LambdaEntryMemoryStateSplitOperation::tryMapMemoryNodeIdToOutput(
505 *lambdaEntrySplit,
506 aa::PointsToGraph::externalMemoryNode);
507
508 // If the lambda does not route this memory state at all, it is effectively invariant
509 if (result != nullptr && argument != nullptr)
510 {
511 // If the lambda body has an edge for this memory state, and it is not invariant, we must stop
512 if (result->origin() != argument)
513 continue;
514 }
515
516 // If we get here, the memory state is invariant, and can be routed around the call
517 const auto output =
518 CallExitMemoryStateSplitOperation::tryMapMemoryNodeIdToOutput(*callExitSplit, memoryNodeId);
519 const auto input = CallEntryMemoryStateMergeOperation::tryMapMemoryNodeIdToInput(
520 *callEntryMerge,
521 memoryNodeId);
522
523 JLM_ASSERT(output);
524 if (!input)
525 continue;
526
527 output->divert_users(input->origin());
528 }
529}
530
531void
532InvariantValueRedirection::redirectLoadMemoryStates(rvsdg::SimpleNode & loadNode)
533{
534 if (LoadOperation::LoadedValueOutput(loadNode).IsDead())
535 {
536 for (auto & memoryStateOutput : LoadOperation::MemoryStateOutputs(loadNode))
537 {
538 auto & memoryStateInput = LoadOperation::MapMemoryStateOutputToInput(memoryStateOutput);
539 memoryStateOutput.divert_users(memoryStateInput.origin());
540 }
541 }
542}
543
544}
Call operation class.
Definition call.hpp:251
Get address of compiled function object.
static rvsdg::Node & Create(rvsdg::Region &region, IntegerValueRepresentation representation)
static std::unique_ptr< Statistics > Create(const util::FilePath &sourceFile)
static void redirectGammaOutputConstants(rvsdg::GammaNode &gammaNode)
static void redirectGammaOutputs(rvsdg::GammaNode &gammaNode)
void Run(rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
void redirectInSubregions(rvsdg::StructuralNode &structuralNode)
static void createAndRun(rvsdg::RvsdgModule &rvsdgModule, Configuration configuration)
static void redirectThetaOutputs(rvsdg::ThetaNode &thetaNode)
static void redirectThetaGammaOutputs(rvsdg::ThetaNode &thetaNode)
Interpret pointer as callable function.
UndefValueOperation class.
static Output & create(Region &region, ControlValueRepresentation value)
Definition control.hpp:122
const ControlValueRepresentation & value() const noexcept
Definition control.hpp:116
Conditional operator / pattern matching.
Definition gamma.hpp:99
std::vector< ExitVar > GetExitVars() const
Gets all exit variables for this gamma.
Definition gamma.cpp:381
Region & GetRootRegion() const noexcept
Definition graph.hpp:99
void divert_to(Output *new_origin)
Definition node.cpp:64
Output * origin() const noexcept
Definition node.hpp:58
rvsdg::Region * subregion() const noexcept
Definition lambda.hpp:138
rvsdg::Region * region() const noexcept
Definition node.hpp:761
size_t noutputs() const noexcept
Definition node.hpp:644
void divert_users(jlm::rvsdg::Output *new_origin)
Definition node.hpp:301
std::variant< Node *, Region * > GetOwner() const noexcept
Definition node.hpp:378
bool IsDead() const noexcept
Definition node.hpp:295
A phi node represents the fixpoint of mutually recursive definitions.
Definition Phi.hpp:46
static std::vector< rvsdg::LambdaNode * > ExtractLambdaNodes(const PhiNode &phiNode)
Definition Phi.cpp:226
Represent acyclic RVSDG subgraphs.
Definition region.hpp:213
void prune(bool recursive)
Definition region.cpp:326
rvsdg::StructuralNode * node() const noexcept
Definition region.hpp:301
NodeRange Nodes() noexcept
Definition region.hpp:375
const std::optional< util::FilePath > & SourceFilePath() const noexcept
Graph & Rvsdg() noexcept
NodeOutput * output(size_t index) const noexcept
SubregionIteratorRange Subregions()
std::vector< LoopVar > GetLoopVars() const
Returns all loop variables.
Definition theta.cpp:193
void CollectDemandedStatistics(std::unique_ptr< Statistics > statistics)
Statistics Interface.
util::Timer & GetTimer(const std::string &name)
util::Timer & AddTimer(std::string name)
void start() noexcept
Definition time.hpp:54
void stop() noexcept
Definition time.hpp:67
#define JLM_ASSERT(x)
Definition common.hpp:16
Global memory state passed between functions.
static rvsdg::Node * getConstant(const rvsdg::Output &output)
static util::StatisticsCollector statisticsCollector
rvsdg::SimpleNode * tryGetMemoryStateEntrySplit(const rvsdg::LambdaNode &lambdaNode) noexcept
rvsdg::Output & traceOutput(rvsdg::Output &output, bool mayEnterSubregions, const rvsdg::Region *withinRegion)
Definition Trace.cpp:84
std::optional< std::unique_ptr< ThetaGammaPredicateCorrelation > > computeThetaGammaPredicateCorrelation(rvsdg::ThetaNode &thetaNode)
std::optional< GammaSubregionRoles > determineGammaSubregionRoles(const ThetaGammaPredicateCorrelation &correlation)
rvsdg::SimpleNode * tryGetMemoryStateExitMerge(const rvsdg::LambdaNode &lambdaNode) noexcept
void MatchTypeWithDefault(T &obj, const Fns &... fns)
Pattern match over subclass type of given object with default handler.
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition theta.hpp:266
void MatchType(T &obj, const Fns &... fns)
Pattern match over subclass type of given object.
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872
std::optional< rvsdg::Output * > GetGammaInvariantOrigin(const GammaNode &gamma, const GammaNode::ExitVar &exitvar)
Determines whether a gamma exit var is path-invariant.
Definition gamma.cpp:509
detail::TopDownTraverserGeneric< false > TopDownTraverser
Traverser for visiting every node in a region in a top down order.
Description of a loop-carried variable.
Definition theta.hpp:50
rvsdg::Output * pre
Variable before iteration (input argument to subregion).
Definition theta.hpp:58
rvsdg::Output * output
Variable at loop exit (output of theta).
Definition theta.hpp:66
rvsdg::Input * post
Variable after iteration (output result from subregion).
Definition theta.hpp:62