Jlm
Loading...
Searching...
No Matches
Load.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
13#include <jlm/llvm/ir/Trace.hpp>
14#include <jlm/rvsdg/delta.hpp>
15#include <jlm/rvsdg/lambda.hpp>
16#include <jlm/util/HashSet.hpp>
17
18namespace jlm::llvm
19{
20
22
23bool
24LoadNonVolatileOperation::operator==(const Operation & other) const noexcept
25{
26 auto operation = dynamic_cast<const LoadNonVolatileOperation *>(&other);
27 return operation && operation->narguments() == narguments()
28 && operation->GetLoadedType() == GetLoadedType()
29 && operation->GetAlignment() == GetAlignment();
30}
31
32std::string
34{
35 return "Load";
36}
37
38std::unique_ptr<rvsdg::Operation>
40{
41 return std::make_unique<LoadNonVolatileOperation>(*this);
42}
43
44/*
45 If the producer of a load's address is an alloca, then we can remove
46 all state edges originating from other allocas.
47
48 a1 s1 = AllocaOperation ...
49 a2 s2 = AllocaOperation ...
50 s3 = mux_op s1
51 v sl1 sl2 sl3 = load_op a1 s1 s2 s3
52 =>
53 ...
54 v sl1 sl3 = load_op a1 s1 s3
55*/
56static bool
57is_load_alloca_reducible(const std::vector<rvsdg::Output *> & operands)
58{
59 auto address = operands[0];
60
61 auto [allocaNode, allocaOperation] =
63 if (!allocaOperation)
64 return false;
65
66 for (size_t n = 1; n < operands.size(); n++)
67 {
68 const auto node = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[n]);
69 if (is<AllocaOperation>(node) && node != allocaNode)
70 return true;
71 }
72
73 return false;
74}
75
76static bool
77is_reducible_state(const rvsdg::Output * state, const rvsdg::Node * loadalloca)
78{
79 auto [storeNode, storeOperation] =
81 if (storeOperation)
82 {
83 auto address = StoreNonVolatileOperation::AddressInput(*storeNode).origin();
84 auto [allocaNode, allocaOperation] =
86 if (allocaOperation && allocaNode != loadalloca)
87 return true;
88 }
89
90 return false;
91}
92
93/*
94 a1 sa1 = AllocaOperation ...
95 a2 sa2 = AllocaOperation ...
96 ss1 = store_op a1 ... sa1
97 ss2 = store_op a2 ... sa2
98 ... = load_op a1 ss1 ss2
99 =>
100 ...
101 ... = load_op a1 ss1
102*/
103static bool
105 const LoadNonVolatileOperation & op,
106 const std::vector<rvsdg::Output *> & operands)
107{
108 auto address = operands[0];
109
110 if (operands.size() == 2)
111 return false;
112
113 auto [allocaNode, allocaOperation] =
115 if (!allocaOperation)
116 {
117 return false;
118 }
119
120 size_t redstates = 0;
121 for (size_t n = 1; n < operands.size(); n++)
122 {
123 auto state = operands[n];
124 if (is_reducible_state(state, allocaNode))
125 redstates++;
126 }
127
128 return redstates == op.NumMemoryStates() || redstates == 0 ? false : true;
129}
130
131/*
132 v so1 so2 so3 = load_op a si1 si1 si1
133 =>
134 v so1 = load_op a si1
135*/
136static bool
137is_multiple_origin_reducible(const std::vector<rvsdg::Output *> & operands)
138{
139 const util::HashSet<rvsdg::Output *> states(std::next(operands.begin()), operands.end());
140 return states.Size() != operands.size() - 1;
141}
142
143static std::vector<rvsdg::Output *>
145 const LoadNonVolatileOperation & op,
146 const std::vector<rvsdg::Output *> & operands)
147{
148 const auto allocaNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[0]);
149
150 std::vector<rvsdg::Output *> loadstates;
151 std::vector<rvsdg::Output *> otherstates;
152 for (size_t n = 1; n < operands.size(); n++)
153 {
154 const auto node = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[n]);
155 if (!is<AllocaOperation>(node) || node == allocaNode)
156 loadstates.push_back(operands[n]);
157 else
158 otherstates.push_back(operands[n]);
159 }
160
162 operands[0],
163 loadstates,
164 op.GetLoadedType(),
165 op.GetAlignment());
166
167 std::vector<rvsdg::Output *> results(1, ld[0]);
168 results.insert(results.end(), std::next(ld.begin()), ld.end());
169 results.insert(results.end(), otherstates.begin(), otherstates.end());
170 return results;
171}
172
173static std::vector<rvsdg::Output *>
175 const LoadNonVolatileOperation & op,
176 const std::vector<rvsdg::Output *> & operands)
177{
178 auto address = operands[0];
179 const auto allocaNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*address);
180
181 std::vector<rvsdg::Output *> new_loadstates;
182 std::vector<rvsdg::Output *> results(operands.size(), nullptr);
183 for (size_t n = 1; n < operands.size(); n++)
184 {
185 auto state = operands[n];
186 if (is_reducible_state(state, allocaNode))
187 results[n] = state;
188 else
189 new_loadstates.push_back(state);
190 }
191
193 operands[0],
194 new_loadstates,
195 op.GetLoadedType(),
196 op.GetAlignment());
197
198 results[0] = ld[0];
199 for (size_t n = 1, s = 1; n < results.size(); n++)
200 {
201 if (results[n] == nullptr)
202 results[n] = ld[s++];
203 }
204
205 return results;
206}
207
208static std::vector<rvsdg::Output *>
210 const LoadNonVolatileOperation & op,
211 const std::vector<rvsdg::Output *> & operands)
212{
213 JLM_ASSERT(operands.size() > 1);
214 const auto address = operands[0];
215
216 std::vector<rvsdg::Output *> newInputStates;
217 std::unordered_map<rvsdg::Output *, size_t> stateIndexMap;
218 for (size_t n = 1; n < operands.size(); n++)
219 {
220 auto state = operands[n];
221 if (stateIndexMap.find(state) == stateIndexMap.end())
222 {
223 const size_t resultIndex = 1 + newInputStates.size(); // loaded value + states seen so far
224 newInputStates.push_back(state);
225 stateIndexMap[state] = resultIndex;
226 }
227 }
228
229 const auto loadResults = LoadNonVolatileOperation::Create(
230 address,
231 newInputStates,
232 op.GetLoadedType(),
233 op.GetAlignment());
234
235 std::vector<rvsdg::Output *> results(operands.size(), nullptr);
236 results[0] = loadResults[0];
237 for (size_t n = 1; n < operands.size(); n++)
238 {
239 auto state = operands[n];
240 JLM_ASSERT(stateIndexMap.find(state) != stateIndexMap.end());
241 results[n] = loadResults[stateIndexMap[state]];
242 }
243
244 return results;
245}
246
247std::optional<std::vector<rvsdg::Output *>>
249 const LoadNonVolatileOperation & operation,
250 const std::vector<rvsdg::Output *> & operands)
251{
252 // We do not need to check further if no state edge is provided to the load
253 if (operands.size() < 2)
254 {
255 return std::nullopt;
256 }
257 const auto loadAddressOperand = operands[0];
258
259 // Check that the first state edge originates from a store
260 auto firstState = operands[1];
261 auto [storeNode, storeOperation] =
263 if (!storeOperation)
264 {
265 return std::nullopt;
266 }
267 const auto storeAddressOperand = StoreNonVolatileOperation::AddressInput(*storeNode).origin();
268 const auto storeValueOperand = StoreNonVolatileOperation::StoredValueInput(*storeNode).origin();
269
270 if (loadAddressOperand != storeAddressOperand)
271 {
272 return std::nullopt;
273 }
274
275 // Check that all state edges to the load originate from the same store
276 if (storeOperation->NumMemoryStates() != operation.NumMemoryStates())
277 {
278 return std::nullopt;
279 }
280 for (size_t n = 1; n < operands.size(); n++)
281 {
282 auto state = operands[n];
283 const auto node = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*state);
284 if (node != storeNode)
285 {
286 return std::nullopt;
287 }
288 }
289
290 // Check that the loaded and stored value type are the same
291 //
292 // FIXME: This is too restrictive and can be improved upon by inserting truncation or narrowing
293 // operations instead. For example, a store of a 32 bit integer followed by a load of a 8 bit
294 // integer can be converted to a trunc operation.
295 auto loadedValueType = operation.GetLoadedType();
296 auto & storedValueType = *storeValueOperand->Type();
297 if (*loadedValueType != storedValueType)
298 {
299 return std::nullopt;
300 }
301
302 std::vector results(1, storeValueOperand);
303 results.insert(results.end(), std::next(operands.begin()), operands.end());
304
305 return results;
306}
307
308std::optional<std::vector<rvsdg::Output *>>
310 const LoadNonVolatileOperation & operation,
311 const std::vector<rvsdg::Output *> & operands)
312{
313 if (is_load_alloca_reducible(operands))
314 return perform_load_alloca_reduction(operation, operands);
315
316 return std::nullopt;
317}
318
319std::optional<std::vector<rvsdg::Output *>>
321 const LoadNonVolatileOperation & operation,
322 const std::vector<rvsdg::Output *> & operands)
323{
324 if (is_load_store_state_reducible(operation, operands))
325 return perform_load_store_state_reduction(operation, operands);
326
327 return std::nullopt;
328}
329
330std::optional<std::vector<rvsdg::Output *>>
332 const LoadNonVolatileOperation & operation,
333 const std::vector<rvsdg::Output *> & operands)
334{
335 if (is_multiple_origin_reducible(operands))
336 return perform_multiple_origin_reduction(operation, operands);
337
338 return std::nullopt;
339}
340
341static std::optional<size_t>
343{
344 auto [allocaNode, allocaOperation] =
346 if (allocaOperation)
347 {
348 return GetTypeAllocSize(*allocaOperation->allocatedType());
349 }
350
351 if (const auto deltaNode = rvsdg::TryGetOwnerNode<rvsdg::DeltaNode>(output))
352 {
353 const auto deltaOperation =
354 util::assertedCast<const LlvmDeltaOperation>(&deltaNode->GetOperation());
355 return GetTypeAllocSize(*deltaOperation->Type());
356 }
357
358 if (const auto llvmImport = dynamic_cast<const LlvmGraphImport *>(&output))
359 {
360 return GetTypeAllocSize(*llvmImport->ValueType());
361 }
362
363 return std::nullopt;
364}
365
366std::optional<std::vector<rvsdg::Output *>>
368 const LoadNonVolatileOperation & loadOperation,
369 const std::vector<rvsdg::Output *> & operands)
370{
371 JLM_ASSERT(operands.size() >= 1);
372 const auto address = operands[0];
373
374 auto [ioBarrierNode, ioBarrierOperation] =
376 if (!ioBarrierOperation)
377 return std::nullopt;
378
379 auto & barredAddress = *IOBarrierOperation::BarredInput(*ioBarrierNode).origin();
380 const auto & pointerOrigin = TracePointerOriginPrecise(barredAddress);
381 const auto allocationSizeInBytes = getAllocationSizeInBytes(*pointerOrigin.BasePointer);
382 if (!allocationSizeInBytes.has_value())
383 return std::nullopt;
384
385 size_t offsetInBytes = 0;
386 if (const auto offsetInBytesOpt = pointerOrigin.getOffsetInBytes(); offsetInBytesOpt.has_value())
387 {
388 offsetInBytes = offsetInBytesOpt.value();
389 }
390
391 // This transformation is only valid if the affected bytes by the load operation are within the
392 // size of the allocation site.
393 if (offsetInBytes + GetTypeStoreSize(*loadOperation.GetLoadedType())
394 > allocationSizeInBytes.value())
395 return std::nullopt;
396
397 auto & loadNode = CreateNode(
398 barredAddress,
399 { std::next(operands.begin()), operands.end() },
400 loadOperation.GetLoadedType(),
401 loadOperation.GetAlignment());
402
403 return { outputs(&loadNode) };
404}
405
407
408bool
409LoadVolatileOperation::operator==(const Operation & other) const noexcept
410{
411 auto operation = dynamic_cast<const LoadVolatileOperation *>(&other);
412 return operation && operation->narguments() == narguments()
413 && operation->GetLoadedType() == GetLoadedType()
414 && operation->GetAlignment() == GetAlignment();
415}
416
417std::string
419{
420 return "LoadVolatile";
421}
422
423std::unique_ptr<rvsdg::Operation>
425{
426 return std::make_unique<LoadVolatileOperation>(*this);
427}
428
431 rvsdg::Region & region,
432 std::unique_ptr<LoadVolatileOperation> loadOperation,
433 const std::vector<rvsdg::Output *> & operands)
434{
435 return rvsdg::SimpleNode::Create(region, std::move(loadOperation), operands);
436}
437
438}
static rvsdg::Input & BarredInput(const rvsdg::Node &node) noexcept
Definition IOBarrier.hpp:70
static std::optional< std::vector< rvsdg::Output * > > normalizeIOBarrierAddress(const LoadNonVolatileOperation &loadOperation, const std::vector< rvsdg::Output * > &operands)
Redirect the address operand of the LoadNonVolatileOperation node from an IOBarrierOperation node whe...
Definition Load.cpp:367
std::unique_ptr< Operation > copy() const override
Definition Load.cpp:39
static std::unique_ptr< llvm::ThreeAddressCode > Create(const Variable *address, const Variable *state, std::shared_ptr< const rvsdg::Type > loadedType, size_t alignment)
Definition Load.hpp:447
static std::optional< std::vector< rvsdg::Output * > > NormalizeLoadStoreState(const LoadNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
If the producer of a load's address is an alloca operation, then we can remove all state edges origin...
Definition Load.cpp:320
~LoadNonVolatileOperation() noexcept override
static std::optional< std::vector< rvsdg::Output * > > NormalizeDuplicateStates(const LoadNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
Remove duplicated state operands.
Definition Load.cpp:331
static rvsdg::SimpleNode & CreateNode(rvsdg::Region &region, std::unique_ptr< LoadNonVolatileOperation > loadOperation, const std::vector< rvsdg::Output * > &operands)
Definition Load.hpp:469
static std::optional< std::vector< rvsdg::Output * > > NormalizeLoadAlloca(const LoadNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
If the producer of a load's address is an alloca operation, then we can remove all state edges origin...
Definition Load.cpp:309
std::string debug_string() const override
Definition Load.cpp:33
static std::optional< std::vector< rvsdg::Output * > > NormalizeLoadStore(const LoadNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
Forwards the value from a store operation.
Definition Load.cpp:248
size_t NumMemoryStates() const noexcept
Definition Load.hpp:69
std::shared_ptr< const rvsdg::Type > GetLoadedType() const noexcept
Definition Load.hpp:63
size_t GetAlignment() const noexcept
Definition Load.hpp:57
std::string debug_string() const override
Definition Load.cpp:418
~LoadVolatileOperation() noexcept override
static rvsdg::SimpleNode & CreateNode(rvsdg::Region &region, std::unique_ptr< LoadVolatileOperation > loadOperation, const std::vector< rvsdg::Output * > &operands)
Definition Load.cpp:430
std::unique_ptr< Operation > copy() const override
Definition Load.cpp:424
static rvsdg::Input & StoredValueInput(const rvsdg::Node &node) noexcept
Definition Store.hpp:84
static rvsdg::Input & AddressInput(const rvsdg::Node &node) noexcept
Definition Store.hpp:75
Output * origin() const noexcept
Definition node.hpp:58
Represent acyclic RVSDG subgraphs.
Definition region.hpp:213
static SimpleNode & Create(Region &region, std::unique_ptr< Operation > operation, const std::vector< rvsdg::Output * > &operands)
size_t narguments() const noexcept
Definition operation.cpp:17
std::size_t Size() const noexcept
Definition HashSet.hpp:187
#define JLM_ASSERT(x)
Definition common.hpp:16
Global memory state passed between functions.
static bool is_load_alloca_reducible(const std::vector< rvsdg::Output * > &operands)
Definition Load.cpp:57
size_t GetTypeAllocSize(const rvsdg::Type &type)
Definition types.cpp:473
TracedPointerOrigin TracePointerOriginPrecise(const rvsdg::Output &p)
Definition Trace.cpp:153
static std::vector< rvsdg::Output * > perform_load_alloca_reduction(const LoadNonVolatileOperation &op, const std::vector< rvsdg::Output * > &operands)
Definition Load.cpp:144
static bool is_load_store_state_reducible(const LoadNonVolatileOperation &op, const std::vector< rvsdg::Output * > &operands)
Definition Load.cpp:104
size_t GetTypeStoreSize(const rvsdg::Type &type)
Definition types.cpp:386
static std::optional< size_t > getAllocationSizeInBytes(const rvsdg::Output &output)
Definition Load.cpp:342
static bool is_multiple_origin_reducible(const std::vector< rvsdg::Output * > &operands)
Definition Load.cpp:137
static std::vector< rvsdg::Output * > perform_multiple_origin_reduction(const LoadNonVolatileOperation &op, const std::vector< rvsdg::Output * > &operands)
Definition Load.cpp:209
static bool is_reducible_state(const rvsdg::Output *state, const rvsdg::Node *loadalloca)
Definition Load.cpp:77
static std::vector< rvsdg::Output * > perform_load_store_state_reduction(const LoadNonVolatileOperation &op, const std::vector< rvsdg::Output * > &operands)
Definition Load.cpp:174
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872