[clang][Interp] Support destructors
authorTimm Bäder <tbaeder@redhat.com>
Thu, 5 Jan 2023 12:40:54 +0000 (13:40 +0100)
committerTimm Bäder <tbaeder@redhat.com>
Sun, 5 Mar 2023 09:02:42 +0000 (10:02 +0100)
Use the existing local variable cleanup infrastructure to implement
destruction.

Differential Revision: https://reviews.llvm.org/D137070

clang/lib/AST/Interp/ByteCodeExprGen.cpp
clang/lib/AST/Interp/ByteCodeExprGen.h
clang/lib/AST/Interp/ByteCodeStmtGen.cpp
clang/test/AST/Interp/cxx20.cpp

index bc682c9..488d07e 100644 (file)
@@ -26,10 +26,10 @@ namespace clang {
 namespace interp {
 
 /// Scope used to handle temporaries in toplevel variable declarations.
-template <class Emitter> class DeclScope final : public LocalScope<Emitter> {
+template <class Emitter> class DeclScope final : public VariableScope<Emitter> {
 public:
   DeclScope(ByteCodeExprGen<Emitter> *Ctx, const ValueDecl *VD)
-      : LocalScope<Emitter>(Ctx), Scope(Ctx->P, VD) {}
+      : VariableScope<Emitter>(Ctx), Scope(Ctx->P, VD) {}
 
   void addExtended(const Scope::Local &Local) override {
     return this->addLocal(Local);
@@ -1857,6 +1857,80 @@ void ByteCodeExprGen<Emitter>::emitCleanup() {
     C->emitDestruction();
 }
 
+/// When calling this, we have a pointer of the local-to-destroy
+/// on the stack.
+/// Emit destruction of record types (or arrays of record types).
+/// FIXME: Handle virtual destructors.
+template <class Emitter>
+bool ByteCodeExprGen<Emitter>::emitRecordDestruction(const Descriptor *Desc) {
+  assert(Desc);
+  assert(!Desc->isPrimitive());
+  assert(!Desc->isPrimitiveArray());
+
+  // Arrays.
+  if (Desc->isArray()) {
+    const Descriptor *ElemDesc = Desc->ElemDesc;
+    const Record *ElemRecord = ElemDesc->ElemRecord;
+    assert(ElemRecord); // This is not a primitive array.
+
+    if (const CXXDestructorDecl *Dtor = ElemRecord->getDestructor();
+        Dtor && !Dtor->isTrivial()) {
+      for (ssize_t I = Desc->getNumElems() - 1; I >= 0; --I) {
+        if (!this->emitConstUint64(I, SourceInfo{}))
+          return false;
+        if (!this->emitArrayElemPtrUint64(SourceInfo{}))
+          return false;
+        if (!this->emitRecordDestruction(Desc->ElemDesc))
+          return false;
+      }
+    }
+    return this->emitPopPtr(SourceInfo{});
+  }
+
+  const Record *R = Desc->ElemRecord;
+  assert(R);
+  // First, destroy all fields.
+  for (const Record::Field &Field : llvm::reverse(R->fields())) {
+    const Descriptor *D = Field.Desc;
+    if (!D->isPrimitive() && !D->isPrimitiveArray()) {
+      if (!this->emitDupPtr(SourceInfo{}))
+        return false;
+      if (!this->emitGetPtrField(Field.Offset, SourceInfo{}))
+        return false;
+      if (!this->emitRecordDestruction(D))
+        return false;
+    }
+  }
+
+  // FIXME: Unions need to be handled differently here. We don't want to
+  //   call the destructor of its members.
+
+  // Now emit the destructor and recurse into base classes.
+  if (const CXXDestructorDecl *Dtor = R->getDestructor();
+      Dtor && !Dtor->isTrivial()) {
+    const Function *DtorFunc = getFunction(Dtor);
+    if (DtorFunc && DtorFunc->isConstexpr()) {
+      assert(DtorFunc->hasThisPointer());
+      assert(DtorFunc->getNumParams() == 1);
+      if (!this->emitDupPtr(SourceInfo{}))
+        return false;
+      if (!this->emitCall(DtorFunc, SourceInfo{}))
+        return false;
+    }
+  }
+
+  for (const Record::Base &Base : llvm::reverse(R->bases())) {
+    if (!this->emitGetPtrBase(Base.Offset, SourceInfo{}))
+      return false;
+    if (!this->emitRecordDestruction(Base.Desc))
+      return false;
+  }
+  // FIXME: Virtual bases.
+
+  // Remove the instance pointer.
+  return this->emitPopPtr(SourceInfo{});
+}
+
 namespace clang {
 namespace interp {
 
index 231f39f..4d92927 100644 (file)
@@ -256,6 +256,8 @@ private:
     return FPO.getRoundingMode();
   }
 
+  bool emitRecordDestruction(const Descriptor *Desc);
+
 protected:
   /// Variable to storage mapping.
   llvm::DenseMap<const ValueDecl *, Scope::Local> Locals;
@@ -333,9 +335,20 @@ public:
     this->Ctx->Descriptors[*Idx].emplace_back(Local);
   }
 
+  /// Emit destruction of the local variable. This includes
+  /// object destructors.
   void emitDestruction() override {
     if (!Idx)
       return;
+    // Emit destructor calls for local variables of record
+    // type with a destructor.
+    for (Scope::Local &Local : this->Ctx->Descriptors[*Idx]) {
+      if (!Local.Desc->isPrimitive() && !Local.Desc->isPrimitiveArray()) {
+        this->Ctx->emitGetPtrLocal(Local.Offset, SourceInfo{});
+        this->Ctx->emitRecordDestruction(Local.Desc);
+      }
+    }
+
     this->Ctx->emitDestroy(*Idx, SourceInfo{});
   }
 
index a4be86c..547a24e 100644 (file)
@@ -428,6 +428,7 @@ bool ByteCodeStmtGen<Emitter>::visitBreakStmt(const BreakStmt *S) {
   if (!BreakLabel)
     return false;
 
+  this->emitCleanup();
   return this->jump(*BreakLabel);
 }
 
@@ -436,6 +437,7 @@ bool ByteCodeStmtGen<Emitter>::visitContinueStmt(const ContinueStmt *S) {
   if (!ContinueLabel)
     return false;
 
+  this->emitCleanup();
   return this->jump(*ContinueLabel);
 }
 
index cc37722..480d6e8 100644 (file)
@@ -271,3 +271,250 @@ namespace ConstThis {
                                        // ref-error {{must have constant destruction}} \
                                        // ref-note {{in call to}}
 };
+
+namespace Destructors {
+
+  class Inc final {
+  public:
+    int &I;
+    constexpr Inc(int &I) : I(I) {}
+    constexpr ~Inc() {
+      I++;
+    }
+  };
+
+  class Dec final {
+  public:
+    int &I;
+    constexpr Dec(int &I) : I(I) {}
+    constexpr ~Dec() {
+      I--;
+    }
+  };
+
+
+
+  constexpr int m() {
+    int i = 0;
+    {
+      Inc f1(i);
+      Inc f2(i);
+      Inc f3(i);
+    }
+    return i;
+  }
+  static_assert(m() == 3, "");
+
+
+  constexpr int C() {
+    int i = 0;
+
+    while (i < 10) {
+      Inc inc(i);
+      continue;
+      Dec dec(i);
+    }
+    return i;
+  }
+  static_assert(C() == 10, "");
+
+
+  constexpr int D() {
+    int i = 0;
+
+    {
+      Inc i1(i);
+      {
+        Inc i2(i);
+        return i;
+      }
+    }
+
+    return i;
+  }
+  static_assert(D() == 0, "");
+
+  constexpr int E() {
+    int i = 0;
+
+    for(;;) {
+      Inc i1(i);
+      break;
+    }
+    return i;
+  }
+  static_assert(E() == 1, "");
+
+
+  /// FIXME: This should be rejected, since we call the destructor
+  ///   twice. However, GCC doesn't care either.
+  constexpr int ManualDtor() {
+    int i = 0;
+    {
+      Inc I(i); // ref-note {{destroying object 'I' whose lifetime has already ended}}
+      I.~Inc();
+    }
+    return i;
+  }
+  static_assert(ManualDtor() == 1, ""); // expected-error {{static assertion failed}} \
+                                        // expected-note {{evaluates to '2 == 1'}} \
+                                        // ref-error {{not an integral constant expression}} \
+                                        // ref-note {{in call to 'ManualDtor()'}}
+
+  constexpr void doInc(int &i) {
+    Inc I(i);
+    return;
+  }
+  constexpr int testInc() {
+    int i = 0;
+    doInc(i);
+    return i;
+  }
+  static_assert(testInc() == 1, "");
+  constexpr void doInc2(int &i) {
+    Inc I(i);
+    // No return statement.
+  }
+   constexpr int testInc2() {
+    int i = 0;
+    doInc2(i);
+    return i;
+  }
+  static_assert(testInc2() == 1, "");
+
+
+  namespace DtorOrder {
+    class A {
+      public:
+      int &I;
+      constexpr A(int &I) : I(I) {}
+      constexpr ~A() {
+        I = 1337;
+      }
+    };
+
+    class B : public A {
+      public:
+      constexpr B(int &I) : A(I) {}
+      constexpr ~B() {
+        I = 42;
+      }
+    };
+
+    constexpr int foo() {
+      int i = 0;
+      {
+        B b(i);
+      }
+      return i;
+    }
+
+    static_assert(foo() == 1337);
+  }
+
+  class FieldDtor1 {
+  public:
+    Inc I1;
+    Inc I2;
+    constexpr FieldDtor1(int &I) : I1(I), I2(I){}
+  };
+
+  constexpr int foo2() {
+    int i = 0;
+    {
+      FieldDtor1 FD1(i);
+    }
+    return i;
+  }
+
+  static_assert(foo2() == 2);
+
+  class FieldDtor2 {
+  public:
+    Inc Incs[3];
+    constexpr FieldDtor2(int &I)  : Incs{Inc(I), Inc(I), Inc(I)} {}
+  };
+
+  constexpr int foo3() {
+    int i = 0;
+    {
+      FieldDtor2 FD2(i);
+    }
+    return i;
+  }
+
+  static_assert(foo3() == 3);
+
+  struct ArrD {
+    int index;
+    int *arr;
+    int &p;
+    constexpr ~ArrD() {
+      arr[p] = index;
+      ++p;
+    }
+  };
+  constexpr bool ArrayOrder() {
+    int order[3] = {0, 0, 0};
+    int p = 0;
+    {
+      ArrD ds[3] = {
+        {1, order, p},
+        {2, order, p},
+        {3, order, p},
+      };
+      // ds will be destroyed.
+    }
+    return order[0] == 3 && order[1] == 2 && order[2] == 1;
+  }
+  static_assert(ArrayOrder());
+
+
+  // Static members aren't destroyed.
+  class Dec2 {
+  public:
+    int A = 0;
+    constexpr ~Dec2() {
+      A++;
+    }
+  };
+  class Foo {
+  public:
+    static constexpr Dec2 a;
+    static Dec2 b;
+  };
+  static_assert(Foo::a.A == 0);
+  constexpr bool f() {
+    Foo f;
+    return true;
+  }
+  static_assert(Foo::a.A == 0);
+  static_assert(f());
+  static_assert(Foo::a.A == 0);
+
+
+  struct NotConstexpr {
+    NotConstexpr() {}
+    ~NotConstexpr() {}
+  };
+
+  struct Outer {
+    constexpr Outer() = default;
+    constexpr ~Outer();
+
+    constexpr int foo() {
+      return 12;
+    }
+
+    constexpr int bar()const  {
+      return Outer{}.foo();
+    }
+
+    static NotConstexpr Val;
+  };
+
+  constexpr Outer::~Outer() {}
+
+  constexpr Outer O;
+  static_assert(O.bar() == 12);
+}