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