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  JLM_ASSERT(operation.GetAlignment() == storeOperation->GetAlignment());
299 
300  std::vector results(1, storeValueOperand);
301  results.insert(results.end(), std::next(operands.begin()), operands.end());
302 
303  return results;
304 }
305 
306 std::optional<std::vector<rvsdg::Output *>>
308  const LoadNonVolatileOperation & operation,
309  const std::vector<rvsdg::Output *> & operands)
310 {
312  return perform_load_alloca_reduction(operation, operands);
313 
314  return std::nullopt;
315 }
316 
317 std::optional<std::vector<rvsdg::Output *>>
319  const LoadNonVolatileOperation & operation,
320  const std::vector<rvsdg::Output *> & operands)
321 {
322  if (is_load_store_state_reducible(operation, operands))
323  return perform_load_store_state_reduction(operation, operands);
324 
325  return std::nullopt;
326 }
327 
328 std::optional<std::vector<rvsdg::Output *>>
330  const LoadNonVolatileOperation & operation,
331  const std::vector<rvsdg::Output *> & operands)
332 {
334  return perform_multiple_origin_reduction(operation, operands);
335 
336  return std::nullopt;
337 }
338 
339 std::optional<std::vector<rvsdg::Output *>>
341  const LoadNonVolatileOperation & operation,
342  const std::vector<rvsdg::Output *> & operands)
343 {
344  JLM_ASSERT(operands.size() >= 1);
345  const auto address = operands[0];
346 
347  auto [ioBarrierNode, ioBarrierOperation] =
348  rvsdg::TryGetSimpleNodeAndOptionalOp<IOBarrierOperation>(*address);
349  if (!ioBarrierOperation)
350  return std::nullopt;
351 
352  auto & barredAddress = *IOBarrierOperation::BarredInput(*ioBarrierNode).origin();
353  const auto & tracedAddress = llvm::traceOutput(barredAddress);
354  if (!rvsdg::IsOwnerNodeOperation<AllocaOperation>(tracedAddress))
355  return std::nullopt;
356 
357  auto & loadNode = CreateNode(
358  barredAddress,
359  { std::next(operands.begin()), operands.end() },
360  operation.GetLoadedType(),
361  operation.GetAlignment());
362 
363  return { outputs(&loadNode) };
364 }
365 
367 
368 bool
369 LoadVolatileOperation::operator==(const Operation & other) const noexcept
370 {
371  auto operation = dynamic_cast<const LoadVolatileOperation *>(&other);
372  return operation && operation->narguments() == narguments()
373  && operation->GetLoadedType() == GetLoadedType()
374  && operation->GetAlignment() == GetAlignment();
375 }
376 
377 std::string
379 {
380  return "LoadVolatile";
381 }
382 
383 std::unique_ptr<rvsdg::Operation>
385 {
386  return std::make_unique<LoadVolatileOperation>(*this);
387 }
388 
391  rvsdg::Region & region,
392  std::unique_ptr<LoadVolatileOperation> loadOperation,
393  const std::vector<rvsdg::Output *> & operands)
394 {
395  return rvsdg::SimpleNode::Create(region, std::move(loadOperation), operands);
396 }
397 
398 }
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:451
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:340
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:318
~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:329
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:429
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:307
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:378
~LoadVolatileOperation() noexcept override
static rvsdg::SimpleNode & CreateNode(rvsdg::Region &region, std::unique_ptr< LoadVolatileOperation > loadOperation, const std::vector< rvsdg::Output * > &operands)
Definition: Load.cpp:390
std::unique_ptr< Operation > copy() const override
Definition: Load.cpp:384
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)
Definition: Trace.cpp:39
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