Jlm
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 
10 #include <jlm/llvm/ir/Trace.hpp>
11 #include <jlm/llvm/ir/types.hpp>
13 #include <jlm/util/HashSet.hpp>
14 
15 namespace jlm::llvm
16 {
17 
19 
20 bool
21 StoreNonVolatileOperation::operator==(const Operation & other) const noexcept
22 {
23  auto operation = dynamic_cast<const StoreNonVolatileOperation *>(&other);
24  return operation && operation->narguments() == narguments()
25  && operation->GetStoredType() == GetStoredType()
26  && operation->GetAlignment() == GetAlignment();
27 }
28 
29 std::string
31 {
32  return "Store";
33 }
34 
35 std::unique_ptr<rvsdg::Operation>
37 {
38  return std::make_unique<StoreNonVolatileOperation>(*this);
39 }
40 
41 static bool
42 is_store_mux_reducible(const std::vector<jlm::rvsdg::Output *> & operands)
43 {
44  JLM_ASSERT(operands.size() > 2);
45 
46  const auto memStateMergeNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[2]);
47  if (!is<MemoryStateMergeOperation>(memStateMergeNode))
48  return false;
49 
50  for (size_t n = 2; n < operands.size(); n++)
51  {
52  if (rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[n]) != memStateMergeNode)
53  return false;
54  }
55 
56  return true;
57 }
58 
59 static bool
61  const StoreNonVolatileOperation & store2Op,
62  const std::vector<jlm::rvsdg::Output *> & operands)
63 {
64  JLM_ASSERT(operands.size() > 2);
65 
66  // Try tracing a memory state edge to a previous store
67  const auto [store1Node, store1Op] =
68  rvsdg::TryGetSimpleNodeAndOptionalOp<StoreNonVolatileOperation>(*operands[2]);
69  if (!store1Op)
70  return false;
71 
72  const auto & store1Address = *StoreOperation::AddressInput(*store1Node).origin();
73  const auto & store2Address = *operands[0];
74 
75  // only continue if store1 and store2 have the same address
76  if (&llvm::traceOutput(store1Address) != &llvm::traceOutput(store2Address))
77  return false;
78 
79  // Check that all memory state inputs come from store1Node, and have no other users
80  for (size_t n = 2; n < operands.size(); n++)
81  {
82  if (rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[n]) != store1Node
83  || operands[n]->nusers() != 1)
84  return false;
85  }
86 
87  // Check that store2 fully overwrites store1
88  const auto & store1Type = store1Op->GetStoredType();
89  const auto & store2Type = store2Op.GetStoredType();
90  if (GetTypeStoreSize(store2Type) < GetTypeStoreSize(store1Type))
91  return false;
92 
93  return true;
94 }
95 
96 static bool
97 is_store_alloca_reducible(const std::vector<jlm::rvsdg::Output *> & operands)
98 {
99  if (operands.size() == 3)
100  return false;
101 
102  const auto allocaNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[0]);
103  if (!is<AllocaOperation>(allocaNode))
104  return false;
105 
106  std::unordered_set states(std::next(std::next(operands.begin())), operands.end());
107  if (states.find(allocaNode->output(1)) == states.end())
108  return false;
109 
110  if (allocaNode->output(1)->nusers() != 1)
111  return false;
112 
113  return true;
114 }
115 
116 static bool
117 is_multiple_origin_reducible(const std::vector<jlm::rvsdg::Output *> & operands)
118 {
119  const util::HashSet<rvsdg::Output *> states(std::next(operands.begin(), 2), operands.end());
120  return states.Size() != operands.size() - 2;
121 }
122 
123 static std::vector<jlm::rvsdg::Output *>
125  const StoreNonVolatileOperation & op,
126  const std::vector<jlm::rvsdg::Output *> & operands)
127 {
128  const auto memStateMergeNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[2]);
129  auto memStateMergeOperands = jlm::rvsdg::operands(memStateMergeNode);
130 
131  auto states = StoreNonVolatileOperation::Create(
132  operands[0],
133  operands[1],
134  memStateMergeOperands,
135  op.GetAlignment());
136  return { MemoryStateMergeOperation::Create(states) };
137 }
138 
139 static std::vector<jlm::rvsdg::Output *>
141  const StoreNonVolatileOperation & op,
142  const std::vector<jlm::rvsdg::Output *> & operands)
143 {
145  const auto storeNode = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*operands[2]);
146 
147  auto storeops = jlm::rvsdg::operands(storeNode);
148  std::vector<jlm::rvsdg::Output *> states(std::next(std::next(storeops.begin())), storeops.end());
150 }
151 
152 static std::vector<jlm::rvsdg::Output *>
154  const StoreNonVolatileOperation & op,
155  const std::vector<jlm::rvsdg::Output *> & operands)
156 {
157  auto value = operands[1];
158  auto address = operands[0];
159  auto alloca_state = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*address)->output(1);
160  std::unordered_set<jlm::rvsdg::Output *> states(
161  std::next(std::next(operands.begin())),
162  operands.end());
163 
164  auto outputs =
165  StoreNonVolatileOperation::Create(address, value, { alloca_state }, op.GetAlignment());
166  states.erase(alloca_state);
167  states.insert(outputs[0]);
168  return { states.begin(), states.end() };
169 }
170 
171 static std::vector<jlm::rvsdg::Output *>
173  const StoreNonVolatileOperation & operation,
174  const std::vector<jlm::rvsdg::Output *> & operands)
175 {
176  // FIXME: Unify with the duplicate state removal reduction of the LoadNonVolatile operation
177 
178  JLM_ASSERT(operands.size() > 2);
179  const auto address = operands[0];
180  const auto value = operands[1];
181 
182  std::vector<rvsdg::Output *> newInputStates;
183  std::unordered_map<rvsdg::Output *, size_t> stateIndexMap;
184  for (size_t n = 2; n < operands.size(); n++)
185  {
186  auto state = operands[n];
187  if (stateIndexMap.find(state) == stateIndexMap.end())
188  {
189  const size_t resultIndex = newInputStates.size();
190  newInputStates.push_back(state);
191  stateIndexMap[state] = resultIndex;
192  }
193  }
194 
195  const auto storeResults =
196  StoreNonVolatileOperation::Create(address, value, newInputStates, operation.GetAlignment());
197 
198  std::vector<rvsdg::Output *> results(operation.nresults(), nullptr);
199  for (size_t n = 2; n < operands.size(); n++)
200  {
201  auto state = operands[n];
202  JLM_ASSERT(stateIndexMap.find(state) != stateIndexMap.end());
203  results[n - 2] = storeResults[stateIndexMap[state]];
204  }
205 
206  return results;
207 }
208 
209 std::optional<std::vector<rvsdg::Output *>>
211  const StoreNonVolatileOperation & operation,
212  const std::vector<rvsdg::Output *> & operands)
213 {
215  return perform_store_mux_reduction(operation, operands);
216 
217  return std::nullopt;
218 }
219 
220 std::optional<std::vector<rvsdg::Output *>>
222  const StoreNonVolatileOperation & operation,
223  const std::vector<rvsdg::Output *> & operands)
224 {
225  if (is_store_store_reducible(operation, operands))
226  return perform_store_store_reduction(operation, operands);
227 
228  return std::nullopt;
229 }
230 
231 std::optional<std::vector<rvsdg::Output *>>
233  const StoreNonVolatileOperation & operation,
234  const std::vector<rvsdg::Output *> & operands)
235 {
237  return perform_store_alloca_reduction(operation, operands);
238 
239  return std::nullopt;
240 }
241 
242 std::optional<std::vector<rvsdg::Output *>>
244  const StoreNonVolatileOperation & operation,
245  const std::vector<rvsdg::Output *> & operands)
246 {
248  return perform_multiple_origin_reduction(operation, operands);
249 
250  return std::nullopt;
251 }
252 
253 std::optional<std::vector<rvsdg::Output *>>
255  const StoreNonVolatileOperation & operation,
256  const std::vector<rvsdg::Output *> & operands)
257 {
258  JLM_ASSERT(operands.size() >= 2);
259  const auto address = operands[0];
260  const auto value = operands[1];
261 
262  auto [ioBarrierNode, ioBarrierOperation] =
263  rvsdg::TryGetSimpleNodeAndOptionalOp<IOBarrierOperation>(*address);
264  if (!ioBarrierOperation)
265  return std::nullopt;
266 
267  auto & barredAddress = *IOBarrierOperation::BarredInput(*ioBarrierNode).origin();
268  const auto & tracedAddress = llvm::traceOutput(barredAddress);
269  if (!rvsdg::IsOwnerNodeOperation<AllocaOperation>(tracedAddress))
270  return std::nullopt;
271 
272  auto & storeNode = CreateNode(
273  barredAddress,
274  *value,
275  { std::next(operands.begin(), 2), operands.end() },
276  operation.GetAlignment());
277 
278  return { outputs(&storeNode) };
279 }
280 
281 std::optional<std::vector<rvsdg::Output *>>
283  const StoreNonVolatileOperation & operation,
284  const std::vector<rvsdg::Output *> & operands)
285 {
286  JLM_ASSERT(operands.size() >= 2);
287  const auto & address = *operands[0];
288 
289  // We cannot(!) use the traced address in this normalization as it might result in the wrong
290  // number of users. The address can be routed through a structural node where it has multiple
291  // users, but the traced address would still just have a single user.
292  if (!rvsdg::IsOwnerNodeOperation<AllocaOperation>(address))
293  return std::nullopt;
294 
295  if (address.nusers() != 1)
296  return std::nullopt;
297 
298  std::vector newMemoryStateResults(operands.begin() + 2, operands.end());
299  JLM_ASSERT(newMemoryStateResults.size() == operation.NumMemoryStates());
300 
301  return newMemoryStateResults;
302 }
303 
305 
306 bool
307 StoreVolatileOperation::operator==(const Operation & other) const noexcept
308 {
309  auto operation = dynamic_cast<const StoreVolatileOperation *>(&other);
310  return operation && operation->NumMemoryStates() == NumMemoryStates()
311  && operation->GetStoredType() == GetStoredType()
312  && operation->GetAlignment() == GetAlignment();
313 }
314 
315 std::string
317 {
318  return "StoreVolatile";
319 }
320 
321 std::unique_ptr<rvsdg::Operation>
323 {
324  return std::make_unique<StoreVolatileOperation>(*this);
325 }
326 
327 }
static rvsdg::Input & BarredInput(const rvsdg::SimpleNode &node) noexcept
Definition: IOBarrier.hpp:70
static rvsdg::Output * Create(const std::vector< rvsdg::Output * > &operands)
static std::unique_ptr< llvm::ThreeAddressCode > Create(const Variable *address, const Variable *value, const Variable *state, size_t alignment)
Definition: Store.hpp:304
static rvsdg::SimpleNode & CreateNode(rvsdg::Output &address, rvsdg::Output &value, const std::vector< rvsdg::Output * > &memoryStates, size_t alignment)
Definition: Store.hpp:323
static std::optional< std::vector< rvsdg::Output * > > NormalizeIOBarrierAllocaAddress(const StoreNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
Redirect the address operand of the StoreNonVolatileOperation from an IOBarrierOperation when it can ...
Definition: Store.cpp:254
std::string debug_string() const override
Definition: Store.cpp:30
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:210
~StoreNonVolatileOperation() noexcept override
static std::optional< std::vector< rvsdg::Output * > > normalizeStoreAllocaSingleUser(const StoreNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
Definition: Store.cpp:282
std::unique_ptr< Operation > copy() const override
Definition: Store.cpp:36
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:232
static std::optional< std::vector< rvsdg::Output * > > NormalizeStoreStore(const StoreNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
Removes a duplicated store to the same address.
Definition: Store.cpp:221
static std::optional< std::vector< rvsdg::Output * > > NormalizeDuplicateStates(const StoreNonVolatileOperation &operation, const std::vector< rvsdg::Output * > &operands)
Remove duplicated state operands.
Definition: Store.cpp:243
const rvsdg::Type & GetStoredType() const noexcept
Definition: Store.hpp:63
size_t GetAlignment() const noexcept
Definition: Store.hpp:57
size_t NumMemoryStates() const noexcept
Definition: Store.hpp:69
static rvsdg::Input & AddressInput(const rvsdg::Node &node) noexcept
Definition: Store.hpp:75
std::string debug_string() const override
Definition: Store.cpp:316
~StoreVolatileOperation() noexcept override
std::unique_ptr< Operation > copy() const override
Definition: Store.cpp:322
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.
static std::vector< jlm::rvsdg::Output * > perform_store_mux_reduction(const StoreNonVolatileOperation &op, const std::vector< jlm::rvsdg::Output * > &operands)
Definition: Store.cpp:124
rvsdg::Output & traceOutput(rvsdg::Output &output, const rvsdg::Region *withinRegion)
Definition: Trace.cpp:54
static std::vector< jlm::rvsdg::Output * > perform_store_alloca_reduction(const StoreNonVolatileOperation &op, const std::vector< jlm::rvsdg::Output * > &operands)
Definition: Store.cpp:153
static bool is_store_mux_reducible(const std::vector< jlm::rvsdg::Output * > &operands)
Definition: Store.cpp:42
size_t GetTypeStoreSize(const rvsdg::Type &type)
Definition: types.cpp:386
static std::vector< jlm::rvsdg::Output * > perform_store_store_reduction(const StoreNonVolatileOperation &op, const std::vector< jlm::rvsdg::Output * > &operands)
Definition: Store.cpp:140
static std::vector< rvsdg::Output * > perform_multiple_origin_reduction(const LoadNonVolatileOperation &op, const std::vector< rvsdg::Output * > &operands)
Definition: Load.cpp:205
static bool is_multiple_origin_reducible(const std::vector< rvsdg::Output * > &operands)
Definition: Load.cpp:133
static bool is_store_store_reducible(const StoreNonVolatileOperation &store2Op, const std::vector< jlm::rvsdg::Output * > &operands)
Definition: Store.cpp:60
static bool is_store_alloca_reducible(const std::vector< jlm::rvsdg::Output * > &operands)
Definition: Store.cpp:97
static std::vector< jlm::rvsdg::Output * > operands(const Node *node)
Definition: node.hpp:1049
static std::vector< jlm::rvsdg::Output * > outputs(const Node *node)
Definition: node.hpp:1058