Destroy class hierarchy when destroying class with a base (#2378)

Depends on #2361, #2421

Add support for destructors of base classes.
Features:
- Call destructors from derived to base class
- Support addressing `TupleValue` using a new `Member` variant

Changes:
- Update `StepDestroy()` to recursively call destructors from derived to base class
- Add new `Member` variant and `IndexedValue` struct to be usable with `TupleValue`
This commit is contained in:
Adrien Leravat
2022-12-12 09:59:03 -08:00
committed by GitHub
parent 0bf4d11ced
commit e37a69a6d5
5 changed files with 171 additions and 53 deletions
+2
View File
@@ -139,10 +139,12 @@ cc_library(
":heap",
":stack",
"//common:check",
"//common:error",
"//common:ostream",
"//explorer/ast",
"//explorer/common:arena",
"//explorer/common:error_builders",
"//explorer/common:source_location",
"@llvm-project//llvm:Support",
],
)
+1 -1
View File
@@ -281,7 +281,7 @@ class DestroyAction : public Action {
// lvalue: Address of the object to be destroyed
// value: The value to be destroyed
// In most cases the lvalue address points to value
// In the case that the member of a class is to be destroyed, points
// In the case that the member of a class is to be destroyed,
// the lvalue points to the address of the class object
// and the value is the member of the class
explicit DestroyAction(Nonnull<const LValue*> lvalue,
+74 -52
View File
@@ -8,6 +8,7 @@
#include <iterator>
#include <map>
#include <memory>
#include <optional>
#include <random>
#include <utility>
@@ -15,11 +16,13 @@
#include <vector>
#include "common/check.h"
#include "common/error.h"
#include "explorer/ast/declaration.h"
#include "explorer/ast/element.h"
#include "explorer/ast/expression.h"
#include "explorer/common/arena.h"
#include "explorer/common/error_builders.h"
#include "explorer/common/source_location.h"
#include "explorer/interpreter/action.h"
#include "explorer/interpreter/action_stack.h"
#include "explorer/interpreter/stack.h"
@@ -1991,64 +1994,83 @@ auto Interpreter::StepDeclaration() -> ErrorOr<Success> {
}
auto Interpreter::StepDestroy() -> ErrorOr<Success> {
// TODO: find a way to avoid dyn_cast in this code, and instead use static
// type information the way the compiler would.
Action& act = todo_.CurrentAction();
DestroyAction& destroy_act = cast<DestroyAction>(act);
if (act.pos() == 0) {
if (const auto* class_obj =
dyn_cast<NominalClassValue>(destroy_act.value())) {
const auto& class_type = cast<NominalClassType>(class_obj->type());
const auto& class_dec = class_type.declaration();
if (class_dec.destructor().has_value()) {
return CallDestructor(*class_dec.destructor(), class_obj);
}
}
}
if (const auto* tuple = dyn_cast<TupleValue>(destroy_act.value())) {
if (tuple->elements().size() > 0) {
int index = tuple->elements().size() - act.pos() - 1;
if (index >= 0) {
const auto& item = tuple->elements()[index];
if (const auto* class_obj = dyn_cast<NominalClassValue>(item)) {
const auto& class_type = cast<NominalClassType>(class_obj->type());
const auto& class_dec = class_type.declaration();
if (class_dec.destructor().has_value()) {
return CallDestructor(*class_dec.destructor(), class_obj);
}
const Action& act = todo_.CurrentAction();
const auto& destroy_act = cast<DestroyAction>(act);
switch (destroy_act.value()->kind()) {
case Value::Kind::NominalClassValue: {
const auto* class_obj = cast<NominalClassValue>(destroy_act.value());
const auto& class_decl =
cast<NominalClassType>(class_obj->type()).declaration();
const int member_count = class_decl.members().size();
if (act.pos() == 0) {
// Run the destructor, if there is one.
if (auto destructor = class_decl.destructor()) {
return CallDestructor(*destructor, class_obj);
} else {
return todo_.RunAgain();
}
if (item->kind() == Value::Kind::TupleValue) {
return todo_.Spawn(
std::make_unique<DestroyAction>(destroy_act.lvalue(), item));
}
// Type of tuple element is integral type e.g. i32
// or the type has no destructor
}
}
}
if (act.pos() > 0) {
if (const auto* class_obj =
dyn_cast<NominalClassValue>(destroy_act.value())) {
const auto& class_type = cast<NominalClassType>(class_obj->type());
const auto& class_dec = class_type.declaration();
int index = class_dec.members().size() - act.pos();
if (index >= 0 && index < static_cast<int>(class_dec.members().size())) {
const auto& member = class_dec.members()[index];
} else if (act.pos() <= member_count) {
// Destroy members.
const int index = class_decl.members().size() - act.pos();
const auto& member = class_decl.members()[index];
if (const auto* var = dyn_cast<VariableDeclaration>(member)) {
Address object = destroy_act.lvalue()->address();
Address mem = object.ElementAddress(arena_->New<NamedElement>(var));
SourceLocation source_loc("destructor", 1);
auto v = heap_.Read(mem, source_loc);
return todo_.Spawn(
std::make_unique<DestroyAction>(destroy_act.lvalue(), *v));
const Address object = destroy_act.lvalue()->address();
const Address var_addr =
object.ElementAddress(arena_->New<NamedElement>(var));
const auto v = heap_.Read(var_addr, SourceLocation("destructor", 1));
CARBON_CHECK(v.ok())
<< "Failed to read member `" << var->binding().name()
<< "` from class `" << class_decl.name() << "`";
return todo_.Spawn(std::make_unique<DestroyAction>(
arena_->New<LValue>(var_addr), *v));
} else {
return todo_.RunAgain();
}
} else if (act.pos() == member_count + 1) {
// Destroy the parent, if there is one.
if (auto base = class_obj->base()) {
const Address obj_addr = destroy_act.lvalue()->address();
const Address base_addr =
obj_addr.ElementAddress(arena_->New<BaseElement>(class_obj));
return todo_.Spawn(std::make_unique<DestroyAction>(
arena_->New<LValue>(base_addr), base.value()));
} else {
return todo_.RunAgain();
}
} else {
todo_.Pop();
return Success();
}
}
case Value::Kind::TupleValue: {
const auto* tuple = cast<TupleValue>(destroy_act.value());
const auto element_count = tuple->elements().size();
if (static_cast<size_t>(act.pos()) < element_count) {
const size_t index = element_count - act.pos() - 1;
const auto& item = tuple->elements()[index];
const auto object_addr = destroy_act.lvalue()->address();
Address field_address = object_addr.ElementAddress(
arena_->New<PositionalElement>(index, item));
if (item->kind() == Value::Kind::NominalClassValue ||
item->kind() == Value::Kind::TupleValue) {
return todo_.Spawn(std::make_unique<DestroyAction>(
arena_->New<LValue>(field_address), item));
} else {
// The tuple element's type is an integral type (e.g., i32)
// or the type doesn't support destruction.
return todo_.RunAgain();
}
} else {
todo_.Pop();
return Success();
}
}
default:
// These declarations have no run-time effects.
todo_.Pop();
return Success();
}
todo_.Pop();
return Success();
CARBON_FATAL() << "Unreachable";
}
auto Interpreter::StepCleanUp() -> ErrorOr<Success> {
+35
View File
@@ -0,0 +1,35 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// AUTOUPDATE
// RUN: %{explorer-run}
// RUN: %{explorer-run-trace}
// CHECK:STDOUT: DESTRUCTOR C 3
// CHECK:STDOUT: DESTRUCTOR A 1
// CHECK:STDOUT: result: 1
package ExplorerTest api;
base class A {
destructor[self: Self]{
Print("DESTRUCTOR A {0}", self.a);
}
var a: i32;
}
base class B extends A {
var b: i32;
}
class C extends B {
destructor[self: Self]{
Print("DESTRUCTOR C {0}", self.c);
}
var c: i32;
}
fn Main() -> i32 {
var c: C = { .base={ .base={ .a=1 }, .b=2}, .c=3 };
return 1;
}
@@ -0,0 +1,59 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// AUTOUPDATE
// RUN: %{explorer-run}
// RUN: %{explorer-run-trace}
// CHECK:STDOUT: Destroying 0 (C)
// CHECK:STDOUT: Destroying 1
// CHECK:STDOUT: Destroying 2
// CHECK:STDOUT: Destroying 3 (B)
// CHECK:STDOUT: Destroying 4
// CHECK:STDOUT: Destroying 5
// CHECK:STDOUT: Destroying 6 (A)
// CHECK:STDOUT: Destroying 7
// CHECK:STDOUT: Destroying 8
// CHECK:STDOUT: result: 0
package ExplorerTest api;
base class Data {
fn Make(x: i32) -> Data { return {.data = x}; }
destructor[self: Self]{
Print("Destroying {0}", self.data);
}
var data: i32;
}
base class A {
fn Make() -> A { return {.a1=Data.Make(8), .a2=Data.Make(7)}; }
destructor[self: Self]{
Print("Destroying 6 (A)");
}
var a1: Data;
var a2: Data;
}
base class B extends A {
fn Make() -> B { return {.base = A.Make(), .b1=Data.Make(5), .b2=Data.Make(4)}; }
destructor[self: Self]{
Print("Destroying 3 (B)");
}
var b1: Data;
var b2: Data;
}
class C extends B {
fn Make() -> C { return {.base = B.Make(), .c1=Data.Make(2), .c2=Data.Make(1)}; }
destructor[self: Self]{
Print("Destroying 0 (C)");
}
var c1: Data;
var c2: Data;
}
fn Main() -> i32 {
var c: C = C.Make();
return 0;
}