Jlm
Loading...
Searching...
No Matches
Store.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
12#include <jlm/llvm/ir/Trace.hpp>
13#include <jlm/llvm/ir/types.hpp>
14#include <jlm/rvsdg/delta.hpp>
16#include <jlm/util/HashSet.hpp>
17
18namespace jlm::llvm
19{
20
22
23bool
24StoreNonVolatileOperation::operator==(const Operation & other) const noexcept
25{
26 auto operation = dynamic_cast<const StoreNonVolatileOperation *>(&other);
27 return operation && operation->narguments() == narguments()
28 && operation->GetStoredType() == GetStoredType()
29 && operation->GetAlignment() == GetAlignment();
30}
31
32std::string
34{
35 return "Store";
36}
37
38std::unique_ptr<rvsdg::Operation>
40{
41 return std::make_unique<StoreNonVolatileOperation>(*this);
42}
43
44static bool
45is_store_mux_reducible(const std::vector<jlm::rvsdg::Output *> & operands)
46{
47 JLM_ASSERT(operands.size() > 2);
48
49 const auto memStateMergeNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[2]);
50 if (!is<MemoryStateMergeOperation>(memStateMergeNode))
51 return false;
52
53 for (size_t n = 2; n < operands.size(); n++)
54 {
55 if (rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[n]) != memStateMergeNode)
56 return false;
57 }
58
59 return true;
60}
61
62static bool
63is_store_alloca_reducible(const std::vector<jlm::rvsdg::Output *> & operands)
64{
65 if (operands.size() == 3)
66 return false;
67
68 const auto allocaNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[0]);
69 if (!is<AllocaOperation>(allocaNode))
70 return false;
71
72 std::unordered_set states(std::next(std::next(operands.begin())), operands.end());
73 if (states.find(allocaNode->output(1)) == states.end())
74 return false;
75
76 if (allocaNode->output(1)->nusers() != 1)
77 return false;
78
79 return true;
80}
81
82static bool
83is_multiple_origin_reducible(const std::vector<jlm::rvsdg::Output *> & operands)
84{
85 const util::HashSet<rvsdg::Output *> states(std::next(operands.begin(), 2), operands.end());
86 return states.Size() != operands.size() - 2;
87}
88
89static std::vector<jlm::rvsdg::Output *>
92 const std::vector<jlm::rvsdg::Output *> & operands)
93{
94 const auto memStateMergeNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[2]);
95 auto memStateMergeOperands = jlm::rvsdg::operands(memStateMergeNode);
96
98 operands[0],
99 operands[1],
100 memStateMergeOperands,
101 op.GetAlignment());
102 return { MemoryStateMergeOperation::Create(states) };
103}
104
105static std::vector<jlm::rvsdg::Output *>
107 const StoreNonVolatileOperation & op,
108 const std::vector<jlm::rvsdg::Output *> & operands)
109{
110 auto value = operands[1];
111 auto address = operands[0];
112 auto alloca_state = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*address)->output(1);
113 std::unordered_set<jlm::rvsdg::Output *> states(
114 std::next(std::next(operands.begin())),
115 operands.end());
116
117 auto outputs =
118 StoreNonVolatileOperation::Create(address, value, { alloca_state }, op.GetAlignment());
119 states.erase(alloca_state);
120 states.insert(outputs[0]);
121 return { states.begin(), states.end() };
122}
123
124static std::vector<jlm::rvsdg::Output *>
126 const StoreNonVolatileOperation & operation,
127 const std::vector<jlm::rvsdg::Output *> & operands)
128{
129 // FIXME: Unify with the duplicate state removal reduction of the LoadNonVolatile operation
130
131 JLM_ASSERT(operands.size() > 2);
132 const auto address = operands[0];
133 const auto value = operands[1];
134
135 std::vector<rvsdg::Output *> newInputStates;
136 std::unordered_map<rvsdg::Output *, size_t> stateIndexMap;
137 for (size_t n = 2; n < operands.size(); n++)
138 {
139 auto state = operands[n];
140 if (stateIndexMap.find(state) == stateIndexMap.end())
141 {
142 const size_t resultIndex = newInputStates.size();
143 newInputStates.push_back(state);
144 stateIndexMap[state] = resultIndex;
145 }
146 }
147
148 const auto storeResults =
149 StoreNonVolatileOperation::Create(address, value, newInputStates, operation.GetAlignment());
150
151 std::vector<rvsdg::Output *> results(operation.nresults(), nullptr);
152 for (size_t n = 2; n < operands.size(); n++)
153 {
154 auto state = operands[n];
155 JLM_ASSERT(stateIndexMap.find(state) != stateIndexMap.end());
156 results[n - 2] = storeResults[stateIndexMap[state]];
157 }
158
159 return results;
160}
161
162std::optional<std::vector<rvsdg::Output *>>
164 const StoreNonVolatileOperation & operation,
165 const std::vector<rvsdg::Output *> & operands)
166{
167 if (is_store_mux_reducible(operands))
168 return perform_store_mux_reduction(operation, operands);
169
170 return std::nullopt;
171}
172
173std::optional<std::vector<rvsdg::Output *>>
175 const StoreNonVolatileOperation & store2Op,
176 const std::vector<rvsdg::Output *> & operands)
177{
178 if (store2Op.NumMemoryStates() == 0)
179 {
180 // We have a store node without memory state edges. This can happen if the compiler can
181 // statically prove that the store node's address is a null pointer.
182 return std::nullopt;
183 }
184
185 JLM_ASSERT(operands.size() > 2);
186 auto & store2Address = *operands[0];
187 auto & store2Value = *operands[1];
188 const auto & store2FirstMemoryState = *operands[2];
189
190 // Try tracing a memory state edge to a previous store
191 const auto [store1Node, store1Op] =
193 if (!store1Op)
194 return std::nullopt;
195
196 // Store1 and store2 must have the same address
197 auto & store1Address = *AddressInput(*store1Node).origin();
198 if (&llvm::traceOutput(store1Address) != &llvm::traceOutput(store2Address))
199 return std::nullopt;
200
201 // Check that all memory state inputs originate from store1 AND have no other users
202 std::vector<rvsdg::Output *> newMemoryStates;
203 for (size_t n = 2; n < operands.size(); n++)
204 {
205 auto & memoryState = *operands[n];
206 JLM_ASSERT(is<MemoryStateType>(memoryState.Type()));
207
208 if (rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(memoryState) == store1Node
209 && memoryState.nusers() == 1)
210 {
211 auto & memoryStateInput = MapMemoryStateOutputToInput(memoryState);
212 newMemoryStates.push_back(memoryStateInput.origin());
213 }
214 else
215 {
216 return std::nullopt;
217 }
218 }
219
220 // Check that store2 fully overwrites store1
221 const auto & store1Type = store1Op->GetStoredType();
222 const auto & store2Type = store2Op.GetStoredType();
223 if (GetTypeStoreSize(store2Type) < GetTypeStoreSize(store1Type))
224 return std::nullopt;
225
226 return Create(&store2Address, &store2Value, newMemoryStates, store2Op.GetAlignment());
227}
228
229std::optional<std::vector<rvsdg::Output *>>
231 const StoreNonVolatileOperation & operation,
232 const std::vector<rvsdg::Output *> & operands)
233{
234 if (is_store_alloca_reducible(operands))
235 return perform_store_alloca_reduction(operation, operands);
236
237 return std::nullopt;
238}
239
240std::optional<std::vector<rvsdg::Output *>>
242 const StoreNonVolatileOperation & operation,
243 const std::vector<rvsdg::Output *> & operands)
244{
245 if (is_multiple_origin_reducible(operands))
246 return perform_multiple_origin_reduction(operation, operands);
247
248 return std::nullopt;
249}
250
251// FIXME: We have exactly the same function for the
252// LoadNonVolatileOperation::normalizeIOBarrierAddress
253static std::optional<size_t>
255{
256 auto [allocaNode, allocaOperation] =
258 if (allocaOperation)
259 {
260 return GetTypeAllocSize(*allocaOperation->allocatedType());
261 }
262
263 if (const auto deltaNode = rvsdg::TryGetOwnerNode<rvsdg::DeltaNode>(output))
264 {
265 const auto deltaOperation =
266 util::assertedCast<const LlvmDeltaOperation>(&deltaNode->GetOperation());
267 return GetTypeAllocSize(*deltaOperation->Type());
268 }
269
270 if (const auto llvmImport = dynamic_cast<const LlvmGraphImport *>(&output))
271 {
272 return GetTypeAllocSize(*llvmImport->ValueType());
273 }
274
275 return std::nullopt;
276}
277
278std::optional<std::vector<rvsdg::Output *>>
280 const StoreNonVolatileOperation & storeOperation,
281 const std::vector<rvsdg::Output *> & operands)
282{
283 JLM_ASSERT(operands.size() >= 2);
284 const auto address = operands[0];
285 const auto value = operands[1];
286
287 auto [ioBarrierNode, ioBarrierOperation] =
289 if (!ioBarrierOperation)
290 return std::nullopt;
291
292 auto & barredAddress = *IOBarrierOperation::BarredInput(*ioBarrierNode).origin();
293 const auto & pointerOrigin = TracePointerOriginPrecise(barredAddress);
294 const auto allocationSizeInBytes = getAllocationSizeInBytes(*pointerOrigin.BasePointer);
295 if (!allocationSizeInBytes.has_value())
296 return std::nullopt;
297
298 size_t offsetInBytes = 0;
299 if (const auto offsetInBytesOpt = pointerOrigin.getOffsetInBytes(); offsetInBytesOpt.has_value())
300 {
301 offsetInBytes = offsetInBytesOpt.value();
302 }
303
304 // This transformation is only valid if the affected bytes by the store operation are within the
305 // size of the allocation site.
306 if (offsetInBytes + GetTypeStoreSize(storeOperation.GetStoredType())
307 > allocationSizeInBytes.value())
308 return std::nullopt;
309
310 auto & storeNode = CreateNode(
311 barredAddress,
312 *value,
313 { std::next(operands.begin(), 2), operands.end() },
314 storeOperation.GetAlignment());
315
316 return { outputs(&storeNode) };
317}
318
319std::optional<std::vector<rvsdg::Output *>>
321 const StoreNonVolatileOperation & operation,
322 const std::vector<rvsdg::Output *> & operands)
323{
324 JLM_ASSERT(operands.size() >= 2);
325 const auto & address = *operands[0];
326
327 // We cannot(!) use the traced address in this normalization as it might result in the wrong
328 // number of users. The address can be routed through a structural node where it has multiple
329 // users, but the traced address would still just have a single user.
331 return std::nullopt;
332
333 if (address.nusers() != 1)
334 return std::nullopt;
335
336 std::vector newMemoryStateResults(operands.begin() + 2, operands.end());
337 JLM_ASSERT(newMemoryStateResults.size() == operation.NumMemoryStates());
338
339 return newMemoryStateResults;
340}
341
343
344bool
345StoreVolatileOperation::operator==(const Operation & other) const noexcept
346{
347 auto operation = dynamic_cast<const StoreVolatileOperation *>(&other);
348 return operation && operation->NumMemoryStates() == NumMemoryStates()
349 && operation->GetStoredType() == GetStoredType()
350 && operation->GetAlignment() == GetAlignment();
351}
352
353std::string
355{
356 return "StoreVolatile";
357}
358
359std::unique_ptr<rvsdg::Operation>
361{
362 return std::make_unique<StoreVolatileOperation>(*this);
363}
364
365}
static rvsdg::Input & BarredInput(const rvsdg::Node &node) noexcept
Definition IOBarrier.hpp:70
static rvsdg::Output * Create(const std::vector< rvsdg::Output * > &operands)
static std::optional< std::vector< rvsdg::Output * > > normalizeStoreStore(const StoreNonVolatileOperation &store2Op, const std::vector< rvsdg::Output * > &operands)
Removes a duplicated store to the same address.
Definition Store.cpp:174
static std::optional< std::vector< rvsdg::Output * > > normalizeIOBarrierAddress(const StoreNonVolatileOperation &storeOperation, const std::vector< rvsdg::Output * > &operands)
Redirect the address operand of the StoreNonVolatileOperation node from an IOBarrierOperation node wh...
Definition Store.cpp:279
static std::unique_ptr< llvm::ThreeAddressCode > Create(const Variable *address, const Variable *value, const Variable *state, size_t alignment)
Definition Store.hpp:325
std::string debug_string() const override
Definition Store.cpp:33
static std::optional< std::vector< rvsdg::Output * > > NormalizeStoreMux(const StoreNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
Swaps a memory state merge operation and a store operation.
Definition Store.cpp:163
~StoreNonVolatileOperation() noexcept override
static std::optional< std::vector< rvsdg::Output * > > normalizeStoreAllocaSingleUser(const StoreNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
Definition Store.cpp:320
std::unique_ptr< Operation > copy() const override
Definition Store.cpp:39
static std::optional< std::vector< rvsdg::Output * > > NormalizeStoreAlloca(const StoreNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
Removes unnecessary state from a store node when its address originates directly from an alloca node.
Definition Store.cpp:230
static rvsdg::SimpleNode & CreateNode(rvsdg::Output &address, rvsdg::Output &value, const std::vector< rvsdg::Output * > &memoryStates, size_t alignment)
Definition Store.hpp:344
static std::optional< std::vector< rvsdg::Output * > > NormalizeDuplicateStates(const StoreNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
Remove duplicated state operands.
Definition Store.cpp:241
static rvsdg::Input & MapMemoryStateOutputToInput(const rvsdg::Output &output)
Definition Store.hpp:152
size_t GetAlignment() const noexcept
Definition Store.hpp:57
static rvsdg::Input & AddressInput(const rvsdg::Node &node) noexcept
Definition Store.hpp:75
const rvsdg::Type & GetStoredType() const noexcept
Definition Store.hpp:63
size_t NumMemoryStates() const noexcept
Definition Store.hpp:69
std::string debug_string() const override
Definition Store.cpp:354
~StoreVolatileOperation() noexcept override
std::unique_ptr< Operation > copy() const override
Definition Store.cpp:360
Output * origin() const noexcept
Definition node.hpp:58
size_t nresults() const noexcept
Definition operation.cpp:30
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.
size_t GetTypeAllocSize(const rvsdg::Type &type)
Definition types.cpp:473
static std::vector< jlm::rvsdg::Output * > perform_store_mux_reduction(const StoreNonVolatileOperation &op, const std::vector< jlm::rvsdg::Output * > &operands)
Definition Store.cpp:90
rvsdg::Output & traceOutput(rvsdg::Output &output, const rvsdg::Region *withinRegion)
Definition Trace.cpp:62
static std::vector< jlm::rvsdg::Output * > perform_store_alloca_reduction(const StoreNonVolatileOperation &op, const std::vector< jlm::rvsdg::Output * > &operands)
Definition Store.cpp:106
TracedPointerOrigin TracePointerOriginPrecise(const rvsdg::Output &p)
Definition Trace.cpp:153
static bool is_store_mux_reducible(const std::vector< jlm::rvsdg::Output * > &operands)
Definition Store.cpp:45
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_store_alloca_reducible(const std::vector< jlm::rvsdg::Output * > &operands)
Definition Store.cpp:63
static std::vector< jlm::rvsdg::Output * > operands(const Node *node)
Definition node.hpp:1049
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872