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
15#include <jlm/rvsdg/gamma.hpp>
17#include <jlm/rvsdg/theta.hpp>
20
21namespace jlm::llvm
22{
23
25{
26public:
27 ~Statistics() override = default;
28
29 explicit Statistics(const util::FilePath & sourceFile)
31 {}
32
33 void
34 Start() noexcept
35 {
36 AddTimer(Label::Timer).start();
37 }
38
39 void
40 Stop() noexcept
41 {
42 GetTimer(Label::Timer).stop();
43 }
44
45 static std::unique_ptr<Statistics>
46 Create(const util::FilePath & sourceFile)
47 {
48 return std::make_unique<Statistics>(sourceFile);
49 }
50};
51
53
54void
56 rvsdg::RvsdgModule & module,
58{
59 auto statistics = Statistics::Create(module.SourceFilePath().value());
60
61 statistics->Start();
63 statistics->Stop();
64
66}
67
68void
70 rvsdg::RvsdgModule & rvsdgModule,
71 Configuration configuration)
72{
74 InvariantValueRedirection invariantValueRedirection(std::move(configuration));
75 invariantValueRedirection.Run(rvsdgModule, statisticsCollector);
76}
77
78void
80{
81 // We require a topdown traversal in the root region to ensure that a lambda node is visited
82 // before its call nodes. This ensures that all invariant values are redirected in the lambda
83 // subregion before we try to detect invariant call outputs.
84 for (auto node : rvsdg::TopDownTraverser(&rvsdg.GetRootRegion()))
85 {
86 MatchTypeOrFail(
87 *node,
88 [this](const rvsdg::LambdaNode & lambdaNode)
89 {
90 redirectInRegion(*lambdaNode.subregion());
91 },
92 [this](const rvsdg::PhiNode & phiNode)
93 {
94 auto phiLambdaNodes = rvsdg::PhiNode::ExtractLambdaNodes(phiNode);
95 for (auto phiLambdaNode : phiLambdaNodes)
96 {
97 redirectInRegion(*phiLambdaNode->subregion());
98 }
99 },
100 [](const rvsdg::DeltaNode &)
101 {
102 // Nothing needs to be done.
103 // Delta nodes are irrelevant for invariant value redirection.
104 },
105 [](const rvsdg::SimpleNode & simpleNode)
106 {
107 MatchTypeOrFail(
108 simpleNode.GetOperation(),
110 {
111 // Nothing needs to be done.
112 },
114 {
115 // Nothing needs to be done.
116 });
117 });
118 }
119}
120
121void
123{
124 const auto isGammaNode = !!dynamic_cast<rvsdg::GammaNode *>(region.node());
125 const auto isThetaNode = !!dynamic_cast<rvsdg::ThetaNode *>(region.node());
126 const auto isLambdaNode = !!dynamic_cast<rvsdg::LambdaNode *>(region.node());
127 JLM_ASSERT(isGammaNode || isThetaNode || isLambdaNode);
128
129 // We do not need a traverser here and can just iterate through all the nodes of a region as
130 // it is irrelevant in which order we handle the nodes.
131 for (auto & node : region.Nodes())
132 {
134 node,
135 [this](rvsdg::GammaNode & gammaNode)
136 {
137 // Ensure we redirect invariant values of all nodes in the gamma subregions first,
138 // otherwise we might not be able to redirect some of the gamma outputs.
139 redirectInSubregions(gammaNode);
140
142 redirectGammaOutputs(gammaNode);
143 },
144 [this](rvsdg::ThetaNode & thetaNode)
145 {
146 // Ensure we redirect invariant values of all nodes in the theta subregion first,
147 // otherwise we might not be able to redirect some of the theta outputs.
148 redirectInSubregions(thetaNode);
149
151 redirectThetaGammaOutputs(thetaNode);
152
154 redirectThetaOutputs(thetaNode);
155 },
156 [this](rvsdg::SimpleNode & simpleNode)
157 {
159 simpleNode.GetOperation(),
160 [this, &simpleNode](const CallOperation &)
161 {
162 if (configuration_.enableCallOutputRedirection)
163 redirectCallOutputs(simpleNode);
164 },
165 [this, &simpleNode](const LoadOperation &)
166 {
167 if (configuration_.enableLoadMemoryStateRedirection)
168 redirectLoadMemoryStates(simpleNode);
169 });
170 });
171 }
172
173 region.prune(false);
174}
175
176void
178{
179 const auto isGammaNode = !!dynamic_cast<rvsdg::GammaNode *>(&structuralNode);
180 const auto isThetaNode = !!dynamic_cast<rvsdg::ThetaNode *>(&structuralNode);
181 JLM_ASSERT(isGammaNode || isThetaNode);
182
183 for (auto & subregion : structuralNode.Subregions())
184 {
185 redirectInRegion(subregion);
186 }
187}
188
189void
191{
192 for (auto exitVar : gammaNode.GetExitVars())
193 {
194 if (auto invariantOrigin = rvsdg::GetGammaInvariantOrigin(gammaNode, exitVar))
195 {
196 exitVar.output->divert_users(*invariantOrigin);
197 }
198 }
199}
200
201void
203{
204 for (const auto & loopVar : thetaNode.GetLoopVars())
205 {
206 // FIXME: In order to also redirect I/O state type variables, we need to know whether a loop
207 // terminates.
208 if (rvsdg::is<IOStateType>(loopVar.input->Type()))
209 continue;
210
212 loopVar.output->divert_users(loopVar.input->origin());
213 }
214}
215
216void
218{
219 auto correlationOpt = computeThetaGammaPredicateCorrelation(thetaNode);
220 if (!correlationOpt.has_value())
221 {
222 return;
223 }
224 auto & correlation = correlationOpt.value();
225
226 auto subregionRolesOpt = determineGammaSubregionRoles(*correlation);
227 if (!subregionRolesOpt.has_value())
228 {
229 // We could not determine the roles of the gamma subregions. Nothing can be done.
230 return;
231 }
232 auto roles = *subregionRolesOpt;
233 auto & gammaNode = correlation->gammaNode();
234
235 auto divertLoopVar =
236 [&gammaNode](rvsdg::ThetaNode::LoopVar & loopVar, rvsdg::Output & entryVarArgument)
237 {
239 {
240 auto roleVar = gammaNode.MapBranchArgument(entryVarArgument);
241 if (auto entryVar = std::get_if<rvsdg::GammaNode::EntryVar>(&roleVar))
242 {
243 loopVar.post->divert_to(entryVar->input->origin());
244 }
245 }
246 };
247
248 // At this point we can try to redirect the theta node loop variables
249 for (auto & loopVar : thetaNode.GetLoopVars())
250 {
251 if (loopVar.output->IsDead() && loopVar.pre->IsDead())
252 {
253 // The loop variable is completely dead. We do not need to waste any effort on it.
254 continue;
255 }
256
257 auto & loopVarPostOperand = *loopVar.post->origin();
258 if (rvsdg::TryGetOwnerNode<rvsdg::GammaNode>(loopVarPostOperand) != &gammaNode)
259 {
260 // The post value of the loop variable does not originate from the gamma node. Nothing can
261 // be done.
262 continue;
263 }
264 auto [branchResult, _] = gammaNode.MapOutputExitVar(loopVarPostOperand);
265
266 if (loopVar.output->IsDead())
267 {
268 // The loop variables' output is dead, which means only its repetition value is of interest.
269 auto & entryVarArgument = *branchResult[roles.repetitionSubregion->index()]->origin();
270 divertLoopVar(loopVar, entryVarArgument);
271 }
272 else if (loopVar.pre->IsDead())
273 {
274 // The loop variables' pre value is dead, which means only its exit value is of interest.
275 auto & entryVarArgument = *branchResult[roles.exitSubregion->index()]->origin();
276 divertLoopVar(loopVar, entryVarArgument);
277 }
278 }
279}
280
281void
283{
284 JLM_ASSERT(is<CallOperation>(&callNode));
285
286 auto callTypeClassifier = CallOperation::ClassifyCall(callNode);
287 auto callType = callTypeClassifier->GetCallType();
288
289 // FIXME: We currently only support non-recursive direct calls. We would also like to get this
290 // working for recursive direct calls, but that requires a little bit more work as we need to be
291 // able to break the cycles between the recursive calls.
293 return;
294
295 auto & lambdaNode =
296 rvsdg::AssertGetOwnerNode<rvsdg::LambdaNode>(callTypeClassifier->GetLambdaOutput());
297
298 // LLVM permits code where it can happen that the number and type of arguments handed in to the
299 // call node do not agree with the number and type of lambda parameters, even though it is a
300 // direct call. See jlm::tests::LambdaCallArgumentMismatch for an example. In this case, we cannot
301 // redirect the call outputs to the call operand as the types would not align, resulting in type
302 // errors.
303 if (CallOperation::NumArguments(callNode) != lambdaNode.GetFunctionArguments().size())
304 return;
305
306 // First, handle all call outputs where the corresponding function result is invariant
307 const auto results = lambdaNode.GetFunctionResults();
308 JLM_ASSERT(callNode.noutputs() == results.size());
309 for (size_t n = 0; n < callNode.noutputs(); n++)
310 {
311 const auto callOutput = callNode.output(n);
312
313 auto & lambdaResult = *results[n];
314 auto origin = lambdaResult.origin();
315 if (rvsdg::TryGetRegionParentNode<rvsdg::LambdaNode>(*origin) == &lambdaNode)
316 {
317 if (auto ctxvar = lambdaNode.MapBinderContextVar(*origin))
318 {
319 // This is a bound context variable.
320 // FIXME: We would like to get this case working as well, but we need to route the origin
321 // of the respective lambda input to the subregion of the call node.
322 }
323 else
324 {
325 auto callOperand = CallOperation::Argument(callNode, origin->index())->origin();
326 callOutput->divert_users(callOperand);
327 }
328 }
329 }
330
331 // Next, handle lambda bodies that contain memory state split and merge nodes.
332 // Memory state edges can only be routed around the call if the corresponding memory node id
333 // is invariant between the LambdaEntrySplit and the LambdaExitMerge.
334 const auto callExitSplit = CallOperation::tryGetMemoryStateExitSplit(callNode);
335 const auto callEntryMerge = CallOperation::tryGetMemoryStateEntryMerge(callNode);
336 const auto lambdaEntrySplit = tryGetMemoryStateEntrySplit(lambdaNode);
337 const auto lambdaExitMerge = tryGetMemoryStateExitMerge(lambdaNode);
338
339 // Only continue if the call / lambda pair has all four memory state nodes
340 if (callExitSplit == nullptr || callEntryMerge == nullptr || lambdaEntrySplit == nullptr
341 || lambdaExitMerge == nullptr)
342 return;
343
344 const auto callExitSplitOp =
345 *util::assertedCast<const CallExitMemoryStateSplitOperation>(&callExitSplit->GetOperation());
346 for (const auto memoryNodeId : callExitSplitOp.getMemoryNodeIds())
347 {
348 // TODO: This function contains special handling of the external memory node due to
349 // the possiblity of some memory nodes being compressed into the external memory node,
350 // with different functions compressing different sets of memory nodes.
351 // The merge and split nodes should ideally contain explicit information about compression,
352 // to avoid any loss of precision from assuming that missing memory nodes are still present.
353
354 // The memory state edge representing external in the caller can not be re-routed,
355 // because it may represent several memory state edges in the callee.
356 if (memoryNodeId == aa::PointsToGraph::externalMemoryNode)
357 continue;
358
359 // First try to find memory state edges corresponding directly
361 *lambdaExitMerge,
362 memoryNodeId);
364 *lambdaEntrySplit,
365 memoryNodeId);
366
367 // If the memory node is not represented by a distinct memory state edge in the callee,
368 // assume that it is instead represented by the external memory node state edge.
369 if (result == nullptr)
371 *lambdaExitMerge,
373 if (argument == nullptr)
375 *lambdaEntrySplit,
377
378 // If the lambda does not route this memory state at all, it is effectively invariant
379 if (result != nullptr && argument != nullptr)
380 {
381 // If the lambda body has an edge for this memory state, and it is not invariant, we must stop
382 if (result->origin() != argument)
383 continue;
384 }
385
386 // If we get here, the memory state is invariant, and can be routed around the call
387 const auto output =
390 *callEntryMerge,
391 memoryNodeId);
392
393 JLM_ASSERT(output);
394 if (!input)
395 continue;
396
397 output->divert_users(input->origin());
398 }
399}
400
401void
403{
404 if (LoadOperation::LoadedValueOutput(loadNode).IsDead())
405 {
406 for (auto & memoryStateOutput : LoadOperation::MemoryStateOutputs(loadNode))
407 {
408 auto & memoryStateInput = LoadOperation::MapMemoryStateOutputToInput(memoryStateOutput);
409 memoryStateOutput.divert_users(memoryStateInput.origin());
410 }
411 }
412}
413
414}
static jlm::util::StatisticsCollector statisticsCollector
static rvsdg::Input * tryMapMemoryNodeIdToInput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
static rvsdg::Output * tryMapMemoryNodeIdToOutput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
Call operation class.
Definition call.hpp:251
static std::unique_ptr< CallTypeClassifier > ClassifyCall(const rvsdg::SimpleNode &callNode)
Classifies a call node.
Definition call.cpp:49
static rvsdg::SimpleNode * tryGetMemoryStateEntryMerge(const rvsdg::Node &callNode) noexcept
Definition call.hpp:388
static rvsdg::Input * Argument(const rvsdg::Node &node, const size_t n)
Definition call.hpp:310
static size_t NumArguments(const rvsdg::Node &node) noexcept
Definition call.hpp:298
static rvsdg::SimpleNode * tryGetMemoryStateExitSplit(const rvsdg::Node &callNode) noexcept
Definition call.hpp:406
Get address of compiled function object.
static std::unique_ptr< Statistics > Create(const util::FilePath &sourceFile)
static void redirectGammaOutputs(rvsdg::GammaNode &gammaNode)
void Run(rvsdg::RvsdgModule &module, util::StatisticsCollector &statisticsCollector) override
Perform RVSDG transformation.
static void redirectLoadMemoryStates(rvsdg::SimpleNode &loadNode)
void redirectInSubregions(rvsdg::StructuralNode &structuralNode)
static void redirectCallOutputs(rvsdg::SimpleNode &callNode)
static void createAndRun(rvsdg::RvsdgModule &rvsdgModule, Configuration configuration)
static void redirectThetaOutputs(rvsdg::ThetaNode &thetaNode)
static void redirectThetaGammaOutputs(rvsdg::ThetaNode &thetaNode)
static rvsdg::Output * tryMapMemoryNodeIdToOutput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
static rvsdg::Input * tryMapMemoryNodeIdToInput(const rvsdg::SimpleNode &node, MemoryNodeId memoryNodeId)
static rvsdg::Node::OutputIteratorRange MemoryStateOutputs(const rvsdg::Node &node) noexcept
Definition Load.hpp:116
static rvsdg::Output & LoadedValueOutput(const rvsdg::Node &node)
Definition Load.hpp:84
static rvsdg::Input & MapMemoryStateOutputToInput(const rvsdg::Output &output)
Definition Load.hpp:157
Interpret pointer as callable function.
static constexpr NodeIndex externalMemoryNode
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
size_t noutputs() const noexcept
Definition node.hpp:644
void divert_users(jlm::rvsdg::Output *new_origin)
Definition node.hpp:301
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:176
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.
rvsdg::SimpleNode * tryGetMemoryStateEntrySplit(const rvsdg::LambdaNode &lambdaNode) noexcept
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
static bool ThetaLoopVarIsInvariant(const ThetaNode::LoopVar &loopVar) noexcept
Definition theta.hpp:227
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