Jlm
StdLibIntrinsicOperations.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2024 Nico Reißmann <nico.reissmann@gmail.com>
3  * See COPYING for terms of redistribution.
4  */
5 
6 #ifndef JLM_LLVM_IR_OPERATORS_STDLIBINTRINSICOPERATIONS_HPP
7 #define JLM_LLVM_IR_OPERATORS_STDLIBINTRINSICOPERATIONS_HPP
8 
9 #include <jlm/llvm/ir/tac.hpp>
10 #include <jlm/llvm/ir/types.hpp>
11 #include <jlm/rvsdg/bitstring.hpp>
13 
14 namespace jlm::llvm
15 {
16 
24 {
25 protected:
27  const std::vector<std::shared_ptr<const rvsdg::Type>> & operandTypes,
28  const std::vector<std::shared_ptr<const rvsdg::Type>> & resultTypes)
29  : SimpleOperation(operandTypes, resultTypes)
30  {
31  JLM_ASSERT(operandTypes.size() >= 4);
32 
33  auto & dstAddressType = *operandTypes[0];
34  JLM_ASSERT(is<PointerType>(dstAddressType));
35 
36  auto & srcAddressType = *operandTypes[1];
37  JLM_ASSERT(is<PointerType>(srcAddressType));
38 
39  auto & lengthType = *operandTypes[2];
40  if (lengthType != *rvsdg::BitType::Create(32) && lengthType != *rvsdg::BitType::Create(64))
41  {
42  throw util::Error("Expected 32 bit or 64 bit integer type.");
43  }
44 
45  auto & memoryStateType = *operandTypes.back();
46  if (!is<MemoryStateType>(memoryStateType))
47  {
48  throw util::Error("Number of memory states cannot be zero.");
49  }
50  }
51 
52 public:
53  [[nodiscard]] const rvsdg::BitType &
54  LengthType() const noexcept
55  {
56  auto type = std::dynamic_pointer_cast<const rvsdg::BitType>(argument(2));
57  JLM_ASSERT(type != nullptr);
58  return *type;
59  }
60 
61  [[nodiscard]] virtual size_t
62  NumMemoryStates() const noexcept = 0;
63 
68  [[nodiscard]] static rvsdg::Input &
69  destinationInput(const rvsdg::Node & node) noexcept
70  {
71  JLM_ASSERT(is<MemCpyOperation>(&node));
72  const auto input = node.input(0);
73  JLM_ASSERT(is<PointerType>(input->Type()));
74  return *input;
75  }
76 
81  [[nodiscard]] static rvsdg::Input &
82  sourceInput(const rvsdg::Node & node) noexcept
83  {
84  JLM_ASSERT(is<MemCpyOperation>(&node));
85  const auto input = node.input(1);
86  JLM_ASSERT(is<PointerType>(input->Type()));
87  return *input;
88  }
89 
94  [[nodiscard]] static rvsdg::Input &
95  countInput(const rvsdg::Node & node) noexcept
96  {
97  JLM_ASSERT(is<MemCpyOperation>(&node));
98  const auto input = node.input(2);
99  JLM_ASSERT(is<rvsdg::BitType>(input->Type()));
100  return *input;
101  }
102 
106  [[nodiscard]] static rvsdg::Input &
108  {
109  JLM_ASSERT(is<MemoryStateType>(output.Type()));
110  auto [memCpyNode, memCpyOperation] =
111  rvsdg::TryGetSimpleNodeAndOptionalOp<MemCpyOperation>(output);
112  JLM_ASSERT(memCpyOperation);
113  const auto numNonMemoryStateOutputs =
114  memCpyNode->noutputs() - memCpyOperation->NumMemoryStates();
115  JLM_ASSERT(output.index() >= numNonMemoryStateOutputs);
116  const auto numNonMemoryStateInputs = memCpyNode->ninputs() - memCpyOperation->NumMemoryStates();
117  const auto inputIndex = numNonMemoryStateInputs + (output.index() - numNonMemoryStateOutputs);
118  const auto input = memCpyNode->input(inputIndex);
119  JLM_ASSERT(is<MemoryStateType>(input->Type()));
120  return *input;
121  }
122 
126  [[nodiscard]] static rvsdg::Output &
128  {
129  JLM_ASSERT(is<MemoryStateType>(input.Type()));
130  auto [memCpyNode, memCpyOperation] =
131  rvsdg::TryGetSimpleNodeAndOptionalOp<MemCpyOperation>(input);
132  JLM_ASSERT(memCpyOperation);
133  const auto numNonMemoryStateInputs = memCpyNode->ninputs() - memCpyOperation->NumMemoryStates();
134  JLM_ASSERT(input.index() >= numNonMemoryStateInputs);
135  const auto numNonMemoryStateOutputs =
136  memCpyNode->noutputs() - memCpyOperation->NumMemoryStates();
137  const auto outputIndex = numNonMemoryStateOutputs + (input.index() - numNonMemoryStateInputs);
138  const auto output = memCpyNode->output(outputIndex);
139  JLM_ASSERT(is<MemoryStateType>(output->Type()));
140  return *output;
141  }
142 };
143 
150 {
151 public:
153 
154  MemCpyNonVolatileOperation(std::shared_ptr<const rvsdg::Type> lengthType, size_t numMemoryStates)
155  : MemCpyOperation(
156  CreateOperandTypes(std::move(lengthType), numMemoryStates),
157  CreateResultTypes(numMemoryStates))
158  {}
159 
160  bool
161  operator==(const Operation & other) const noexcept override;
162 
163  [[nodiscard]] std::string
164  debug_string() const override;
165 
166  [[nodiscard]] std::unique_ptr<Operation>
167  copy() const override;
168 
169  [[nodiscard]] size_t
170  NumMemoryStates() const noexcept override;
171 
172  static std::unique_ptr<llvm::ThreeAddressCode>
174  const Variable * destination,
175  const Variable * source,
176  const Variable * length,
177  const std::vector<const Variable *> & memoryStates)
178  {
179  std::vector<const Variable *> operands = { destination, source, length };
180  operands.insert(operands.end(), memoryStates.begin(), memoryStates.end());
181 
182  auto operation =
183  std::make_unique<MemCpyNonVolatileOperation>(length->Type(), memoryStates.size());
184  return ThreeAddressCode::create(std::move(operation), operands);
185  }
186 
187  static rvsdg::SimpleNode &
189  rvsdg::Output & destination,
190  rvsdg::Output & source,
191  rvsdg::Output & length,
192  const std::vector<rvsdg::Output *> & memoryStates)
193  {
194  std::vector operands = { &destination, &source, &length };
195  operands.insert(operands.end(), memoryStates.begin(), memoryStates.end());
196 
197  return rvsdg::CreateOpNode<MemCpyNonVolatileOperation>(
198  operands,
199  length.Type(),
200  memoryStates.size());
201  }
202 
203  static std::vector<rvsdg::Output *>
205  rvsdg::Output * destination,
206  rvsdg::Output * source,
207  rvsdg::Output * length,
208  const std::vector<rvsdg::Output *> & memoryStates)
209  {
210  std::vector operands = { destination, source, length };
211  operands.insert(operands.end(), memoryStates.begin(), memoryStates.end());
212 
213  return outputs(&rvsdg::CreateOpNode<MemCpyNonVolatileOperation>(
214  operands,
215  length->Type(),
216  memoryStates.size()));
217  }
218 
219 private:
220  static std::vector<std::shared_ptr<const rvsdg::Type>>
221  CreateOperandTypes(std::shared_ptr<const rvsdg::Type> length, size_t numMemoryStates)
222  {
223  auto pointerType = PointerType::Create();
224  std::vector<std::shared_ptr<const rvsdg::Type>> types = { pointerType, pointerType, length };
225  types.insert(types.end(), numMemoryStates, MemoryStateType::Create());
226  return types;
227  }
228 
229  static std::vector<std::shared_ptr<const rvsdg::Type>>
230  CreateResultTypes(size_t numMemoryStates)
231  {
232  // The memcpy() standard C library call has as return type void*, but LLVM models the
233  // function call nevertheless as:
234  // call void @llvm.memcpy.p0.p0.i64(ptr align 4 %7, ptr align 4 %8, i64 %9, i1 false)
235  //
236  // LLVM simply hands in register %7 (dest pointer) to all users of the return type of memcpy().
237  // Thus, we only need state types as result types of the operation here.
238  return { numMemoryStates, MemoryStateType::Create() };
239  }
240 };
241 
254 {
255 public:
256  ~MemCpyVolatileOperation() noexcept override;
257 
258  MemCpyVolatileOperation(std::shared_ptr<const rvsdg::Type> lengthType, size_t numMemoryStates)
259  : MemCpyOperation(
260  CreateOperandTypes(std::move(lengthType), numMemoryStates),
261  CreateResultTypes(numMemoryStates))
262  {}
263 
264  bool
265  operator==(const Operation & other) const noexcept override;
266 
267  [[nodiscard]] std::string
268  debug_string() const override;
269 
270  [[nodiscard]] std::unique_ptr<Operation>
271  copy() const override;
272 
273  [[nodiscard]] size_t
274  NumMemoryStates() const noexcept override;
275 
276  static std::unique_ptr<llvm::ThreeAddressCode>
278  const Variable & destination,
279  const Variable & source,
280  const Variable & length,
281  const Variable & ioState,
282  const std::vector<const Variable *> & memoryStates)
283  {
284  std::vector<const Variable *> operands = { &destination, &source, &length, &ioState };
285  operands.insert(operands.end(), memoryStates.begin(), memoryStates.end());
286 
287  auto operation = std::make_unique<MemCpyVolatileOperation>(length.Type(), memoryStates.size());
288  return ThreeAddressCode::create(std::move(operation), operands);
289  }
290 
291  static rvsdg::SimpleNode &
293  rvsdg::Output & destination,
294  rvsdg::Output & source,
295  rvsdg::Output & length,
296  rvsdg::Output & ioState,
297  const std::vector<rvsdg::Output *> & memoryStates)
298  {
299  std::vector operands = { &destination, &source, &length, &ioState };
300  operands.insert(operands.end(), memoryStates.begin(), memoryStates.end());
301 
302  return rvsdg::CreateOpNode<MemCpyVolatileOperation>(
303  operands,
304  length.Type(),
305  memoryStates.size());
306  }
307 
308 private:
309  static std::vector<std::shared_ptr<const rvsdg::Type>>
310  CreateOperandTypes(std::shared_ptr<const rvsdg::Type> lengthType, size_t numMemoryStates)
311  {
312  auto pointerType = PointerType::Create();
313  std::vector<std::shared_ptr<const rvsdg::Type>> types = { pointerType,
314  pointerType,
315  std::move(lengthType),
317  types.insert(types.end(), numMemoryStates, MemoryStateType::Create());
318  return types;
319  }
320 
321  static std::vector<std::shared_ptr<const rvsdg::Type>>
322  CreateResultTypes(size_t numMemoryStates)
323  {
324  // The memcpy() standard C library call has as return type void*, but LLVM models the
325  // function call nevertheless as:
326  // call void @llvm.memcpy.p0.p0.i64(ptr align 4 %7, ptr align 4 %8, i64 %9, i1 false)
327  //
328  // LLVM simply hands in register %7 (dest pointer) to all users of the return type of memcpy().
329  // Thus, we only need state types as result types of the operation here.
330  std::vector<std::shared_ptr<const rvsdg::Type>> types(1, IOStateType::Create());
331  types.insert(types.end(), numMemoryStates, MemoryStateType::Create());
332  return types;
333  }
334 };
335 
342 {
343 protected:
345  const std::vector<std::shared_ptr<const rvsdg::Type>> & operandTypes,
346  const std::vector<std::shared_ptr<const rvsdg::Type>> & resultTypes)
347  : SimpleOperation(operandTypes, resultTypes)
348  {
349  JLM_ASSERT(operandTypes.size() >= 4);
350 
351  auto & dstAddressType = *operandTypes[0];
352  JLM_ASSERT(is<PointerType>(dstAddressType));
353 
354  if (auto & valueType = *operandTypes[1]; valueType != *rvsdg::BitType::Create(8))
355  {
356  throw std::runtime_error("Expected 8 bit integer type.");
357  }
358 
359  if (auto & lengthType = *operandTypes[2];
361  {
362  throw util::Error("Expected 32 bit or 64 bit integer type.");
363  }
364 
365  if (auto & memoryStateType = *operandTypes.back(); !is<MemoryStateType>(memoryStateType))
366  {
367  throw util::Error("Number of memory states cannot be zero.");
368  }
369  }
370 
371 public:
375  [[nodiscard]] const rvsdg::BitType &
376  lengthType() const noexcept
377  {
378  const auto type = std::dynamic_pointer_cast<const rvsdg::BitType>(argument(2));
379  JLM_ASSERT(type != nullptr);
380  JLM_ASSERT(type->nbits() == 32 || type->nbits() == 64);
381  return *type;
382  }
383 
387  [[nodiscard]] virtual size_t
388  numMemoryStates() const noexcept = 0;
389 
394  [[nodiscard]] static rvsdg::Input &
395  destinationInput(const rvsdg::Node & node) noexcept
396  {
397  JLM_ASSERT(is<MemSetOperation>(&node));
398  const auto input = node.input(0);
399  JLM_ASSERT(is<PointerType>(input->Type()));
400  return *input;
401  }
402 
407  [[nodiscard]] static rvsdg::Input &
408  valueInput(const rvsdg::Node & node) noexcept
409  {
410  JLM_ASSERT(is<MemSetOperation>(&node));
411  const auto input = node.input(1);
412  JLM_ASSERT(*input->Type() == *rvsdg::BitType::Create(8));
413  return *input;
414  }
415 
420  [[nodiscard]] static rvsdg::Input &
421  lengthInput(const rvsdg::Node & node) noexcept
422  {
423  JLM_ASSERT(is<MemSetOperation>(&node));
424  const auto input = node.input(2);
425  JLM_ASSERT(
426  *input->Type() == *rvsdg::BitType::Create(32)
427  || *input->Type() == *rvsdg::BitType::Create(64));
428  return *input;
429  }
430 
434  [[nodiscard]] static rvsdg::Input &
436  {
437  JLM_ASSERT(is<MemoryStateType>(output.Type()));
438  auto [memsetNode, memsetOperation] =
439  rvsdg::TryGetSimpleNodeAndOptionalOp<MemSetOperation>(output);
440  JLM_ASSERT(memsetOperation);
441  const auto numNonMemoryStateOutputs =
442  memsetNode->noutputs() - memsetOperation->numMemoryStates();
443  JLM_ASSERT(output.index() >= numNonMemoryStateOutputs);
444  const auto numNonMemoryStateInputs = memsetNode->ninputs() - memsetOperation->numMemoryStates();
445  const auto inputIndex = numNonMemoryStateInputs + (output.index() - numNonMemoryStateOutputs);
446  const auto input = memsetNode->input(inputIndex);
447  JLM_ASSERT(is<MemoryStateType>(input->Type()));
448  return *input;
449  }
450 
454  [[nodiscard]] static rvsdg::Output &
456  {
457  JLM_ASSERT(is<MemoryStateType>(input.Type()));
458  auto [memsetNode, memsetOperation] =
459  rvsdg::TryGetSimpleNodeAndOptionalOp<MemSetOperation>(input);
460  JLM_ASSERT(memsetOperation);
461  const auto numNonMemoryStateInputs = memsetNode->ninputs() - memsetOperation->numMemoryStates();
462  JLM_ASSERT(input.index() >= numNonMemoryStateInputs);
463  const auto numNonMemoryStateOutputs =
464  memsetNode->noutputs() - memsetOperation->numMemoryStates();
465  const auto outputIndex = numNonMemoryStateOutputs + (input.index() - numNonMemoryStateInputs);
466  const auto output = memsetNode->output(outputIndex);
467  JLM_ASSERT(is<MemoryStateType>(output->Type()));
468  return *output;
469  }
470 };
471 
479 {
480 public:
481  ~MemSetNonVolatileOperation() noexcept override;
482 
483  MemSetNonVolatileOperation(std::shared_ptr<const rvsdg::Type> lengthType, size_t numMemoryStates)
484  : MemSetOperation(
487  {}
488 
489  bool
490  operator==(const Operation & other) const noexcept override;
491 
492  [[nodiscard]] std::string
493  debug_string() const override;
494 
495  [[nodiscard]] std::unique_ptr<Operation>
496  copy() const override;
497 
498  [[nodiscard]] size_t
499  numMemoryStates() const noexcept override;
500 
501  static std::unique_ptr<ThreeAddressCode>
503  const Variable & destination,
504  const Variable & value,
505  const Variable & length,
506  const std::vector<const Variable *> & memoryStates)
507  {
508  std::vector operands = { &destination, &value, &length };
509  operands.insert(operands.end(), memoryStates.begin(), memoryStates.end());
510 
511  auto operation =
512  std::make_unique<MemSetNonVolatileOperation>(length.Type(), memoryStates.size());
513  return ThreeAddressCode::create(std::move(operation), operands);
514  }
515 
516  static rvsdg::SimpleNode &
518  rvsdg::Output & destination,
519  rvsdg::Output & value,
520  rvsdg::Output & length,
521  const std::vector<rvsdg::Output *> & memoryStates)
522  {
523  std::vector operands = { &destination, &value, &length };
524  operands.insert(operands.end(), memoryStates.begin(), memoryStates.end());
525 
526  return rvsdg::CreateOpNode<MemSetNonVolatileOperation>(
527  operands,
528  length.Type(),
529  memoryStates.size());
530  }
531 
532 private:
533  static std::vector<std::shared_ptr<const rvsdg::Type>>
535  const std::shared_ptr<const rvsdg::Type> & lengthType,
536  const size_t numMemoryStates)
537  {
538  const auto pointerType = PointerType::Create();
539  const auto & valueType = rvsdg::BitType::Create(8);
540  std::vector<std::shared_ptr<const rvsdg::Type>> types = { pointerType, valueType, lengthType };
541  types.insert(types.end(), numMemoryStates, MemoryStateType::Create());
542  return types;
543  }
544 
545  static std::vector<std::shared_ptr<const rvsdg::Type>>
547  {
548  // The memset() standard C library call has as return type void*, but LLVM models the
549  // function call nevertheless as:
550  // call void @llvm.memset.p0.i64(ptr %0, i8 0, i64 8, i1 false)
551  //
552  // LLVM simply hands in register %7 (dest pointer) to all users of the return type of memset().
553  // Thus, we only need state types as result types of the operation here.
555  }
556 };
557 
558 }
559 
560 #endif
static std::shared_ptr< const IOStateType > Create()
Definition: types.cpp:343
std::unique_ptr< Operation > copy() const override
MemCpyNonVolatileOperation(std::shared_ptr< const rvsdg::Type > lengthType, size_t numMemoryStates)
static std::vector< std::shared_ptr< const rvsdg::Type > > CreateOperandTypes(std::shared_ptr< const rvsdg::Type > length, size_t numMemoryStates)
size_t NumMemoryStates() const noexcept override
static std::vector< std::shared_ptr< const rvsdg::Type > > CreateResultTypes(size_t numMemoryStates)
static std::vector< rvsdg::Output * > create(rvsdg::Output *destination, rvsdg::Output *source, rvsdg::Output *length, const std::vector< rvsdg::Output * > &memoryStates)
bool operator==(const Operation &other) const noexcept override
static rvsdg::SimpleNode & createNode(rvsdg::Output &destination, rvsdg::Output &source, rvsdg::Output &length, const std::vector< rvsdg::Output * > &memoryStates)
static std::unique_ptr< llvm::ThreeAddressCode > create(const Variable *destination, const Variable *source, const Variable *length, const std::vector< const Variable * > &memoryStates)
const rvsdg::BitType & LengthType() const noexcept
static rvsdg::Input & mapMemoryStateOutputToInput(const rvsdg::Output &output)
static rvsdg::Input & destinationInput(const rvsdg::Node &node) noexcept
virtual size_t NumMemoryStates() const noexcept=0
MemCpyOperation(const std::vector< std::shared_ptr< const rvsdg::Type >> &operandTypes, const std::vector< std::shared_ptr< const rvsdg::Type >> &resultTypes)
static rvsdg::Input & countInput(const rvsdg::Node &node) noexcept
static rvsdg::Input & sourceInput(const rvsdg::Node &node) noexcept
static rvsdg::Output & mapMemoryStateInputToOutput(const rvsdg::Input &input)
static std::vector< std::shared_ptr< const rvsdg::Type > > CreateOperandTypes(std::shared_ptr< const rvsdg::Type > lengthType, size_t numMemoryStates)
static rvsdg::SimpleNode & CreateNode(rvsdg::Output &destination, rvsdg::Output &source, rvsdg::Output &length, rvsdg::Output &ioState, const std::vector< rvsdg::Output * > &memoryStates)
static std::unique_ptr< llvm::ThreeAddressCode > CreateThreeAddressCode(const Variable &destination, const Variable &source, const Variable &length, const Variable &ioState, const std::vector< const Variable * > &memoryStates)
std::unique_ptr< Operation > copy() const override
~MemCpyVolatileOperation() noexcept override
static std::vector< std::shared_ptr< const rvsdg::Type > > CreateResultTypes(size_t numMemoryStates)
bool operator==(const Operation &other) const noexcept override
size_t NumMemoryStates() const noexcept override
static std::vector< std::shared_ptr< const rvsdg::Type > > createOperandTypes(const std::shared_ptr< const rvsdg::Type > &lengthType, const size_t numMemoryStates)
size_t numMemoryStates() const noexcept override
~MemSetNonVolatileOperation() noexcept override
bool operator==(const Operation &other) const noexcept override
static std::vector< std::shared_ptr< const rvsdg::Type > > createResultTypes(size_t numMemoryStates)
static rvsdg::SimpleNode & createNode(rvsdg::Output &destination, rvsdg::Output &value, rvsdg::Output &length, const std::vector< rvsdg::Output * > &memoryStates)
static std::unique_ptr< ThreeAddressCode > createTac(const Variable &destination, const Variable &value, const Variable &length, const std::vector< const Variable * > &memoryStates)
std::unique_ptr< Operation > copy() const override
virtual size_t numMemoryStates() const noexcept=0
static rvsdg::Input & mapMemoryStateOutputToInput(const rvsdg::Output &output)
const rvsdg::BitType & lengthType() const noexcept
MemSetOperation(const std::vector< std::shared_ptr< const rvsdg::Type >> &operandTypes, const std::vector< std::shared_ptr< const rvsdg::Type >> &resultTypes)
static rvsdg::Input & destinationInput(const rvsdg::Node &node) noexcept
static rvsdg::Input & lengthInput(const rvsdg::Node &node) noexcept
static rvsdg::Output & mapMemoryStateInputToOutput(const rvsdg::Input &input)
static rvsdg::Input & valueInput(const rvsdg::Node &node) noexcept
static std::shared_ptr< const MemoryStateType > Create()
Definition: types.cpp:379
static std::shared_ptr< const PointerType > Create()
Definition: types.cpp:45
static std::unique_ptr< llvm::ThreeAddressCode > create(std::unique_ptr< rvsdg::SimpleOperation > operation, const std::vector< const Variable * > &operands)
Definition: tac.hpp:135
static std::shared_ptr< const BitType > Create(std::size_t nbits)
Creates bit type of specified width.
Definition: type.cpp:45
size_t index() const noexcept
Definition: node.hpp:52
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition: node.hpp:67
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition: node.hpp:366
size_t index() const noexcept
Definition: node.hpp:274
const std::shared_ptr< const rvsdg::Type > & argument(size_t index) const noexcept
Definition: operation.cpp:23
SimpleOperation(std::vector< std::shared_ptr< const jlm::rvsdg::Type >> operands, std::vector< std::shared_ptr< const jlm::rvsdg::Type >> results)
Definition: operation.hpp:61
#define JLM_ASSERT(x)
Definition: common.hpp:16
Global memory state passed between functions.
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