[llvm] [SandboxIR] sandboxir::Use operands (part 1) and uses (part 2) (PR #98251)

via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 9 20:17:29 PDT 2024


https://github.com/vporpo updated https://github.com/llvm/llvm-project/pull/98251

>From b501f0cc0abec8ea88dcabd73c56cf7d5d5af947 Mon Sep 17 00:00:00 2001
From: Vasileios Porpodas <vporpodas at google.com>
Date: Tue, 9 Jul 2024 17:00:43 -0700
Subject: [PATCH 1/2] [SandboxIR] sandboxir::Use Part 1: operands

This patch adds the Use class which is used by the new member functions
of the User class that provide access to the operands.
---
 llvm/include/llvm/SandboxIR/SandboxIR.h    | 129 +++++++++++++++++++++
 llvm/lib/SandboxIR/SandboxIR.cpp           |  56 +++++++++
 llvm/unittests/SandboxIR/SandboxIRTest.cpp |  73 ++++++++++++
 3 files changed, 258 insertions(+)

diff --git a/llvm/include/llvm/SandboxIR/SandboxIR.h b/llvm/include/llvm/SandboxIR/SandboxIR.h
index c84f25f6f5754..8e283f43a6c9d 100644
--- a/llvm/include/llvm/SandboxIR/SandboxIR.h
+++ b/llvm/include/llvm/SandboxIR/SandboxIR.h
@@ -71,6 +71,71 @@ namespace sandboxir {
 class Function;
 class Context;
 class Instruction;
+class User;
+class Value;
+
+/// Represents a Def-use/Use-def edge in SandboxIR.
+/// NOTE: Unlike llvm::Use, this is not an integral part of the use-def chains.
+/// It is also not uniqued and is currently passed by value, so you can have
+/// more than one SBUse objects for the same use-def edge.
+class Use {
+  llvm::Use *LLVMUse;
+  User *User;
+  Context *Ctx;
+
+  /// Don't allow the user to create a sandboxir::Use directly.
+  Use(llvm::Use *LLVMUse, class User *User, Context &Ctx)
+      : LLVMUse(LLVMUse), User(User), Ctx(&Ctx) {}
+  Use() : LLVMUse(nullptr), Ctx(nullptr) {}
+
+  friend class User;               // For constructor
+  friend class OperandUseIterator; // For constructor
+
+public:
+  operator Value *() const { return get(); }
+  Value *get() const;
+  class User *getUser() const { return User; }
+  unsigned getOperandNo() const;
+  Context *getContext() const { return Ctx; }
+  bool operator==(const Use &Other) const {
+    assert(Ctx == Other.Ctx && "Contexts differ!");
+    return LLVMUse == Other.LLVMUse && User == Other.User;
+  }
+  bool operator!=(const Use &Other) const { return !(*this == Other); }
+#ifndef NDEBUG
+  void dump(raw_ostream &OS) const;
+  void dump() const;
+#endif // NDEBUG
+};
+
+/// Returns the operand edge when dereferenced.
+class OperandUseIterator {
+  Use Use;
+  /// Don't let the user create a non-empty SBOperandUseIterator.
+  OperandUseIterator(const class Use &Use) : Use(Use) {}
+  friend class User; // For constructor
+#define DEF_VALUE(ID, CLASS)
+#define DEF_USER(ID, CLASS)
+#define DEF_INSTR(ID, OPC, CLASS) friend class CLASS; // For constructor
+#include "llvm/SandboxIR/SandboxIRValues.def"
+
+public:
+  using difference_type = std::ptrdiff_t;
+  using value_type = class Use;
+  using pointer = value_type *;
+  using reference = value_type &;
+  using iterator_category = std::input_iterator_tag;
+
+  OperandUseIterator() = default;
+  value_type operator*() const;
+  OperandUseIterator &operator++();
+  bool operator==(const OperandUseIterator &Other) const {
+    return Use == Other.Use;
+  }
+  bool operator!=(const OperandUseIterator &Other) const {
+    return !(*this == Other);
+  }
+};
 
 /// A SandboxIR Value has users. This is the base class.
 class Value {
@@ -174,9 +239,61 @@ class User : public Value {
 protected:
   User(ClassID ID, llvm::Value *V, Context &Ctx) : Value(ID, V, Ctx) {}
 
+  /// \Returns the SBUse edge that corresponds to \p OpIdx.
+  /// Note: This is the default implementation that works for instructions that
+  /// match the underlying LLVM instruction. All others should use a different
+  /// implementation.
+  Use getOperandUseDefault(unsigned OpIdx, bool Verify) const;
+  virtual Use getOperandUseInternal(unsigned OpIdx, bool Verify) const = 0;
+  friend class OperandUseIterator; // for getOperandUseInternal()
+
+  /// The default implementation works only for single-LLVMIR-instruction
+  /// SBUsers and only if they match exactly the LLVM instruction.
+  unsigned getUseOperandNoDefault(const Use &Use) const {
+    return Use.LLVMUse->getOperandNo();
+  }
+  /// \Returns the operand index of \p Use.
+  virtual unsigned getUseOperandNo(const Use &Use) const = 0;
+  friend unsigned Use::getOperandNo() const; // For getUseOperandNo()
+
 public:
   /// For isa/dyn_cast.
   static bool classof(const Value *From);
+  using op_iterator = OperandUseIterator;
+  using const_op_iterator = OperandUseIterator;
+  using op_range = iterator_range<op_iterator>;
+  using const_op_range = iterator_range<const_op_iterator>;
+
+  virtual op_iterator op_begin() {
+    assert(isa<llvm::User>(Val) && "Expect User value!");
+    return op_iterator(getOperandUseInternal(0, /*Verify=*/false));
+  }
+  virtual op_iterator op_end() {
+    assert(isa<llvm::User>(Val) && "Expect User value!");
+    return op_iterator(
+        getOperandUseInternal(getNumOperands(), /*Verify=*/false));
+  }
+  virtual const_op_iterator op_begin() const {
+    return const_cast<User *>(this)->op_begin();
+  }
+  virtual const_op_iterator op_end() const {
+    return const_cast<User *>(this)->op_end();
+  }
+
+  op_range operands() { return make_range<op_iterator>(op_begin(), op_end()); }
+  const_op_range operands() const {
+    return make_range<const_op_iterator>(op_begin(), op_end());
+  }
+  Value *getOperand(unsigned OpIdx) const { return getOperandUse(OpIdx).get(); }
+  /// \Returns the operand edge for \p OpIdx. NOTE: This should also work for
+  /// OpIdx == getNumOperands(), which is used for op_end().
+  Use getOperandUse(unsigned OpIdx) const {
+    return getOperandUseInternal(OpIdx, /*Verify=*/true);
+  }
+  virtual unsigned getNumOperands() const {
+    return isa<llvm::User>(Val) ? cast<llvm::User>(Val)->getNumOperands() : 0;
+  }
+
 #ifndef NDEBUG
   void verify() const override {
     assert(isa<llvm::User>(Val) && "Expected User!");
@@ -195,6 +312,9 @@ class Constant : public sandboxir::User {
   Constant(llvm::Constant *C, sandboxir::Context &SBCtx)
       : sandboxir::User(ClassID::Constant, C, SBCtx) {}
   friend class Context; // For constructor.
+  Use getOperandUseInternal(unsigned OpIdx, bool Verify) const final {
+    return getOperandUseDefault(OpIdx, Verify);
+  }
 
 public:
   /// For isa/dyn_cast.
@@ -203,6 +323,9 @@ class Constant : public sandboxir::User {
            From->getSubclassID() == ClassID::Function;
   }
   sandboxir::Context &getParent() const { return getContext(); }
+  unsigned getUseOperandNo(const Use &Use) const final {
+    return getUseOperandNoDefault(Use);
+  }
 #ifndef NDEBUG
   void verify() const final {
     assert(isa<llvm::Constant>(Val) && "Expected Constant!");
@@ -309,11 +432,17 @@ class OpaqueInst : public sandboxir::Instruction {
   OpaqueInst(ClassID SubclassID, llvm::Instruction *I, sandboxir::Context &Ctx)
       : sandboxir::Instruction(SubclassID, Opcode::Opaque, I, Ctx) {}
   friend class Context; // For constructor.
+  Use getOperandUseInternal(unsigned OpIdx, bool Verify) const final {
+    return getOperandUseDefault(OpIdx, Verify);
+  }
 
 public:
   static bool classof(const sandboxir::Value *From) {
     return From->getSubclassID() == ClassID::Opaque;
   }
+  unsigned getUseOperandNo(const Use &Use) const final {
+    return getUseOperandNoDefault(Use);
+  }
   unsigned getNumOfIRInstrs() const final { return 1u; }
 #ifndef NDEBUG
   void verify() const final {
diff --git a/llvm/lib/SandboxIR/SandboxIR.cpp b/llvm/lib/SandboxIR/SandboxIR.cpp
index f64b1145ebf43..47fed24f49d81 100644
--- a/llvm/lib/SandboxIR/SandboxIR.cpp
+++ b/llvm/lib/SandboxIR/SandboxIR.cpp
@@ -13,6 +13,51 @@
 
 using namespace llvm::sandboxir;
 
+Value *Use::get() const { return Ctx->getValue(LLVMUse->get()); }
+
+unsigned Use::getOperandNo() const { return User->getUseOperandNo(*this); }
+
+#ifndef NDEBUG
+void Use::dump(raw_ostream &OS) const {
+  Value *Def = nullptr;
+  if (LLVMUse == nullptr)
+    OS << "<null> LLVM Use! ";
+  else
+    Def = Ctx->getValue(LLVMUse->get());
+  OS << "Def:  ";
+  if (Def == nullptr)
+    OS << "NULL";
+  else
+    OS << *Def;
+  OS << "\n";
+
+  OS << "User: ";
+  if (User == nullptr)
+    OS << "NULL";
+  else
+    OS << *User;
+  OS << "\n";
+
+  OS << "OperandNo: ";
+  if (User == nullptr)
+    OS << "N/A";
+  else
+    OS << getOperandNo();
+  OS << "\n";
+}
+
+void Use::dump() const { dump(dbgs()); }
+#endif // NDEBUG
+
+Use OperandUseIterator::operator*() const { return Use; }
+
+OperandUseIterator &OperandUseIterator::operator++() {
+  assert(Use.LLVMUse != nullptr && "Already at end!");
+  User *User = Use.getUser();
+  Use = User->getOperandUseInternal(Use.getOperandNo() + 1, /*Verify=*/false);
+  return *this;
+}
+
 Value::Value(ClassID SubclassID, llvm::Value *Val, Context &Ctx)
     : SubclassID(SubclassID), Val(Val), Ctx(Ctx) {
 #ifndef NDEBUG
@@ -71,6 +116,17 @@ void Argument::dump() const {
 }
 #endif // NDEBUG
 
+Use User::getOperandUseDefault(unsigned OpIdx, bool Verify) const {
+  assert((!Verify || OpIdx < getNumOperands()) && "Out of bounds!");
+  assert(isa<llvm::User>(Val) && "Non-users have no operands!");
+  llvm::Use *LLVMUse;
+  if (OpIdx != getNumOperands())
+    LLVMUse = &cast<llvm::User>(Val)->getOperandUse(OpIdx);
+  else
+    LLVMUse = cast<llvm::User>(Val)->op_end();
+  return Use(LLVMUse, const_cast<User *>(this), Ctx);
+}
+
 bool User::classof(const Value *From) {
   switch (From->getSubclassID()) {
 #define DEF_VALUE(ID, CLASS)
diff --git a/llvm/unittests/SandboxIR/SandboxIRTest.cpp b/llvm/unittests/SandboxIR/SandboxIRTest.cpp
index 161ee51432cd3..5964d3a792e24 100644
--- a/llvm/unittests/SandboxIR/SandboxIRTest.cpp
+++ b/llvm/unittests/SandboxIR/SandboxIRTest.cpp
@@ -109,6 +109,79 @@ define void @foo(i32 %v1) {
 #endif
 }
 
+TEST_F(SandboxIRTest, Use) {
+  parseIR(C, R"IR(
+define i32 @foo(i32 %v0, i32 %v1) {
+  %add0 = add i32 %v0, %v1
+  ret i32 %add0
+}
+)IR");
+  Function &LLVMF = *M->getFunction("foo");
+  sandboxir::Context Ctx(C);
+
+  BasicBlock *LLVMBB = &*LLVMF.begin();
+  auto LLVMBBIt = LLVMBB->begin();
+  Instruction *LLVMI0 = &*LLVMBBIt++;
+
+  auto &F = *Ctx.createFunction(&LLVMF);
+  auto &BB = *F.begin();
+  auto *Arg0 = F.getArg(0);
+  auto *Arg1 = F.getArg(1);
+  auto It = BB.begin();
+  auto *I0 = &*It++;
+  auto *Ret = &*It++;
+
+  SmallVector<sandboxir::Argument *> Args{Arg0, Arg1};
+  unsigned OpIdx = 0;
+  for (sandboxir::Use Use : I0->operands()) {
+    // Check Use.getOperandNo().
+    EXPECT_EQ(Use.getOperandNo(), OpIdx);
+    // Check Use.getUser().
+    EXPECT_EQ(Use.getUser(), I0);
+    // Check Use.getContext().
+    EXPECT_EQ(Use.getContext(), &Ctx);
+    // Check Use.get().
+    sandboxir::Value *Op = Use.get();
+    EXPECT_EQ(Op, Ctx.getValue(LLVMI0->getOperand(OpIdx)));
+    // Check implicit cast to Value.
+    sandboxir::Value *Cast = Use;
+    EXPECT_EQ(Cast, Op);
+    // Check that Use points to the correct operand.
+    EXPECT_EQ(Op, Args[OpIdx]);
+    // Check getOperand().
+    EXPECT_EQ(Op, I0->getOperand(OpIdx));
+    // Check getOperandUse().
+    EXPECT_EQ(Use, I0->getOperandUse(OpIdx));
+    ++OpIdx;
+  }
+  EXPECT_EQ(OpIdx, 2u);
+
+  // Check Use.operator==() and Use.operator!=().
+  sandboxir::Use UseA = I0->getOperandUse(0);
+  sandboxir::Use UseB = I0->getOperandUse(0);
+  EXPECT_TRUE(UseA == UseB);
+  EXPECT_FALSE(UseA != UseB);
+
+  // Check getNumOperands().
+  EXPECT_EQ(I0->getNumOperands(), 2u);
+  EXPECT_EQ(Ret->getNumOperands(), 1u);
+
+  EXPECT_EQ(Ret->getOperand(0), I0);
+
+#ifndef NDEBUG
+  // Check Use.dump()
+  std::string Buff;
+  raw_string_ostream BS(Buff);
+  BS << "\n";
+  I0->getOperandUse(0).dump(BS);
+  EXPECT_EQ(Buff, R"IR(
+Def:  i32 %v0 ; SB1. (Argument)
+User:   %add0 = add i32 %v0, %v1 ; SB4. (Opaque)
+OperandNo: 0
+)IR");
+#endif // NDEBUG
+}
+
 TEST_F(SandboxIRTest, Function) {
   parseIR(C, R"IR(
 define void @foo(i32 %arg0, i32 %arg1) {

>From e420cad805117540656ad12e1a1e0db74efecee9 Mon Sep 17 00:00:00 2001
From: Vasileios Porpodas <vporpodas at google.com>
Date: Tue, 9 Jul 2024 17:01:25 -0700
Subject: [PATCH 2/2] [SandboxIR] sandboxir::Use Part 2: uses/users

This patch implements uses/users for sandboxir::Value, including
functions like getNumUses(), hasNUsesOrMore() etc.
---
 llvm/include/llvm/SandboxIR/SandboxIR.h    | 97 +++++++++++++++++++++-
 llvm/lib/SandboxIR/SandboxIR.cpp           | 38 +++++++++
 llvm/unittests/SandboxIR/SandboxIRTest.cpp | 55 ++++++++++++
 3 files changed, 189 insertions(+), 1 deletion(-)

diff --git a/llvm/include/llvm/SandboxIR/SandboxIR.h b/llvm/include/llvm/SandboxIR/SandboxIR.h
index 8e283f43a6c9d..da590a3be10ba 100644
--- a/llvm/include/llvm/SandboxIR/SandboxIR.h
+++ b/llvm/include/llvm/SandboxIR/SandboxIR.h
@@ -88,8 +88,10 @@ class Use {
       : LLVMUse(LLVMUse), User(User), Ctx(&Ctx) {}
   Use() : LLVMUse(nullptr), Ctx(nullptr) {}
 
+  friend class Value;              // For constructor
   friend class User;               // For constructor
   friend class OperandUseIterator; // For constructor
+  friend class UserUseIterator;    // For accessing members
 
 public:
   operator Value *() const { return get(); }
@@ -137,6 +139,31 @@ class OperandUseIterator {
   }
 };
 
+/// Returns user edge when dereferenced.
+class UserUseIterator {
+  Use Use;
+  /// Don't let the user create a non-empty UserUseIterator.
+  UserUseIterator(const class Use &Use) : Use(Use) {}
+  friend class Value; // For constructor
+
+public:
+  using difference_type = std::ptrdiff_t;
+  using value_type = class Use;
+  using pointer = value_type *;
+  using reference = value_type &;
+  using iterator_category = std::input_iterator_tag;
+
+  UserUseIterator() = default;
+  value_type operator*() const { return Use; }
+  UserUseIterator &operator++();
+  bool operator==(const UserUseIterator &Other) const {
+    return Use == Other.Use;
+  }
+  bool operator!=(const UserUseIterator &Other) const {
+    return !(*this == Other);
+  }
+};
+
 /// A SandboxIR Value has users. This is the base class.
 class Value {
 public:
@@ -188,9 +215,77 @@ class Value {
   virtual ~Value() = default;
   ClassID getSubclassID() const { return SubclassID; }
 
+  using use_iterator = UserUseIterator;
+  using const_use_iterator = UserUseIterator;
+
+  use_iterator use_begin();
+  const_use_iterator use_begin() const {
+    return const_cast<Value *>(this)->use_begin();
+  }
+  use_iterator use_end() { return use_iterator(Use(nullptr, nullptr, Ctx)); }
+  const_use_iterator use_end() const {
+    return const_cast<Value *>(this)->use_end();
+  }
+
+  iterator_range<use_iterator> uses() {
+    return make_range<use_iterator>(use_begin(), use_end());
+  }
+  iterator_range<const_use_iterator> uses() const {
+    return make_range<const_use_iterator>(use_begin(), use_end());
+  }
+
+  /// Helper for mapped_iterator.
+  struct UseToUser {
+    User *operator()(const Use &Use) const { return &*Use.getUser(); }
+  };
+
+  using user_iterator = mapped_iterator<sandboxir::UserUseIterator, UseToUser>;
+  using const_user_iterator = user_iterator;
+
+  user_iterator user_begin();
+  user_iterator user_end() {
+    return user_iterator(Use(nullptr, nullptr, Ctx), UseToUser());
+  }
+  const_user_iterator user_begin() const {
+    return const_cast<Value *>(this)->user_begin();
+  }
+  const_user_iterator user_end() const {
+    return const_cast<Value *>(this)->user_end();
+  }
+
+  iterator_range<user_iterator> users() {
+    return make_range<user_iterator>(user_begin(), user_end());
+  }
+  iterator_range<const_user_iterator> users() const {
+    return make_range<const_user_iterator>(user_begin(), user_end());
+  }
+  /// \Returns the number of user edges (not necessarily to unique users).
+  /// WARNING: This is a linear-time operation.
+  unsigned getNumUses() const;
+  /// Return true if this value has N uses or more.
+  /// This is logically equivalent to getNumUses() >= N.
+  /// WARNING: This can be expensive, as it is linear to the number of users.
+  bool hasNUsesOrMore(unsigned Num) const {
+    unsigned Cnt = 0;
+    for (auto It = use_begin(), ItE = use_end(); It != ItE; ++It) {
+      if (++Cnt >= Num)
+        return true;
+    }
+    return false;
+  }
+  /// Return true if this Value has exactly N uses.
+  bool hasNUses(unsigned Num) const {
+    unsigned Cnt = 0;
+    for (auto It = use_begin(), ItE = use_end(); It != ItE; ++It) {
+      if (++Cnt > Num)
+        return false;
+    }
+    return Cnt == Num;
+  }
+
   Type *getType() const { return Val->getType(); }
 
-  Context &getContext() const;
+  Context &getContext() const { return Ctx; }
 #ifndef NDEBUG
   /// Should crash if there is something wrong with the instruction.
   virtual void verify() const = 0;
diff --git a/llvm/lib/SandboxIR/SandboxIR.cpp b/llvm/lib/SandboxIR/SandboxIR.cpp
index 47fed24f49d81..f49f5169b4fd3 100644
--- a/llvm/lib/SandboxIR/SandboxIR.cpp
+++ b/llvm/lib/SandboxIR/SandboxIR.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/SandboxIR/SandboxIR.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/Support/Debug.h"
 #include <sstream>
@@ -58,6 +59,20 @@ OperandUseIterator &OperandUseIterator::operator++() {
   return *this;
 }
 
+UserUseIterator &UserUseIterator::operator++() {
+  llvm::Use *&LLVMUse = Use.LLVMUse;
+  assert(LLVMUse != nullptr && "Already at end!");
+  LLVMUse = LLVMUse->getNext();
+  if (LLVMUse == nullptr) {
+    Use.User = nullptr;
+    return *this;
+  }
+  auto *Ctx = Use.Ctx;
+  auto *LLVMUser = LLVMUse->getUser();
+  Use.User = cast_or_null<sandboxir::User>(Ctx->getValue(LLVMUser));
+  return *this;
+}
+
 Value::Value(ClassID SubclassID, llvm::Value *Val, Context &Ctx)
     : SubclassID(SubclassID), Val(Val), Ctx(Ctx) {
 #ifndef NDEBUG
@@ -65,6 +80,29 @@ Value::Value(ClassID SubclassID, llvm::Value *Val, Context &Ctx)
 #endif
 }
 
+Value::use_iterator Value::use_begin() {
+  llvm::Use *LLVMUse = nullptr;
+  if (Val->use_begin() != Val->use_end())
+    LLVMUse = &*Val->use_begin();
+  User *User = LLVMUse != nullptr ? cast_or_null<sandboxir::User>(Ctx.getValue(
+                                        Val->use_begin()->getUser()))
+                                  : nullptr;
+  return use_iterator(Use(LLVMUse, User, Ctx));
+}
+
+Value::user_iterator Value::user_begin() {
+  auto UseBegin = Val->use_begin();
+  auto UseEnd = Val->use_end();
+  bool AtEnd = UseBegin == UseEnd;
+  llvm::Use *LLVMUse = AtEnd ? nullptr : &*UseBegin;
+  User *User =
+      AtEnd ? nullptr
+            : cast_or_null<sandboxir::User>(Ctx.getValue(&*LLVMUse->getUser()));
+  return user_iterator(Use(LLVMUse, User, Ctx), UseToUser());
+}
+
+unsigned Value::getNumUses() const { return range_size(Val->users()); }
+
 #ifndef NDEBUG
 std::string Value::getName() const {
   std::stringstream SS;
diff --git a/llvm/unittests/SandboxIR/SandboxIRTest.cpp b/llvm/unittests/SandboxIR/SandboxIRTest.cpp
index 5964d3a792e24..3439fd86dc98b 100644
--- a/llvm/unittests/SandboxIR/SandboxIRTest.cpp
+++ b/llvm/unittests/SandboxIR/SandboxIRTest.cpp
@@ -143,6 +143,8 @@ define i32 @foo(i32 %v0, i32 %v1) {
     // Check Use.get().
     sandboxir::Value *Op = Use.get();
     EXPECT_EQ(Op, Ctx.getValue(LLVMI0->getOperand(OpIdx)));
+    // Check Use.getUser().
+    EXPECT_EQ(Use.getUser(), I0);
     // Check implicit cast to Value.
     sandboxir::Value *Cast = Use;
     EXPECT_EQ(Cast, Op);
@@ -180,6 +182,59 @@ User:   %add0 = add i32 %v0, %v1 ; SB4. (Opaque)
 OperandNo: 0
 )IR");
 #endif // NDEBUG
+
+  // Check Value.user_begin().
+  sandboxir::Value::user_iterator UIt = I0->user_begin();
+  sandboxir::User *U = *UIt;
+  EXPECT_EQ(U, Ret);
+  // Check Value.uses().
+  unsigned UseCnt = 0;
+  for (sandboxir::Use Use : I0->uses()) {
+    EXPECT_EQ(Use.getUser(), Ret);
+    ++UseCnt;
+  }
+  EXPECT_EQ(UseCnt, 1u);
+  // Check Value.users().
+  unsigned UserCnt = 0;
+  for (sandboxir::User *U : I0->users()) {
+    EXPECT_EQ(U, Ret);
+    ++UserCnt;
+  }
+  EXPECT_EQ(UseCnt, 1u);
+  // Check Value.getNumUses().
+  EXPECT_EQ(I0->getNumUses(), 1u);
+  // Check Value.hasNUsesOrMore().
+  EXPECT_TRUE(I0->hasNUsesOrMore(0u));
+  EXPECT_TRUE(I0->hasNUsesOrMore(1u));
+  EXPECT_FALSE(I0->hasNUsesOrMore(2u));
+  // Check Value.hasNUses().
+  EXPECT_FALSE(I0->hasNUses(0u));
+  EXPECT_TRUE(I0->hasNUses(1u));
+  EXPECT_FALSE(I0->hasNUses(2u));
+}
+
+// Check that the operands/users are counted correctly.
+//  I1
+// /  \
+// \  /
+//  I2
+TEST_F(SandboxIRTest, DuplicateUses) {
+  parseIR(C, R"IR(
+define void @foo(i8 %v) {
+  %I1 = add i8 %v, %v
+  %I2 = add i8 %I1, %I1
+  ret void
+}
+)IR");
+  Function &LLVMF = *M->getFunction("foo");
+  sandboxir::Context Ctx(C);
+  auto *F = Ctx.createFunction(&LLVMF);
+  auto *BB = &*F->begin();
+  auto It = BB->begin();
+  auto *I1 = &*It++;
+  auto *I2 = &*It++;
+  EXPECT_EQ(range_size(I1->users()), 2u);
+  EXPECT_EQ(range_size(I2->operands()), 2u);
 }
 
 TEST_F(SandboxIRTest, Function) {



More information about the llvm-commits mailing list