Feature call destructor for tuples and bug fixes for the destructor process (#2255)

This PR includes the following changes:
*  Added destruction process for tuples
*  Fix: In the current version only the last member of a object can be destroyed
*  Fix: In the current version, the destructor of the object is called after each method call 

I hope it is useful

Co-authored-by: m new <michael.burzan@outlook.de>
Co-authored-by: Geoff Romer <gromer@google.com>
This commit is contained in:
pmqtt
2022-11-04 12:13:24 -07:00
committed by GitHub
co-authored by m new Geoff Romer
parent 4ea3e1951b
commit cef93fba5e
12 changed files with 311 additions and 89 deletions
+22 -14
View File
@@ -25,20 +25,19 @@ RuntimeScope::RuntimeScope(RuntimeScope&& other) noexcept
: locals_(std::move(other.locals_)),
// To transfer ownership of other.allocations_, we have to empty it out.
allocations_(std::exchange(other.allocations_, {})),
heap_(other.heap_),
destructor_scope_(other.destructor_scope_) {}
heap_(other.heap_) {}
auto RuntimeScope::operator=(RuntimeScope&& rhs) noexcept -> RuntimeScope& {
locals_ = std::move(rhs.locals_);
// To transfer ownership of rhs.allocations_, we have to empty it out.
allocations_ = std::exchange(rhs.allocations_, {});
heap_ = rhs.heap_;
destructor_scope_ = rhs.destructor_scope_;
return *this;
}
RuntimeScope::~RuntimeScope() {
for (AllocationId allocation : allocations_) {
for (auto allocation : allocations_) {
// TODO: move this into StepCleanUp
heap_->Deallocate(allocation);
}
}
@@ -52,6 +51,22 @@ void RuntimeScope::Print(llvm::raw_ostream& out) const {
out << "}";
}
void RuntimeScope::Bind(ValueNodeView value_node, Nonnull<const Value*> value) {
CARBON_CHECK(!value_node.constant_value().has_value());
CARBON_CHECK(value->kind() != Value::Kind::LValue);
auto allocation_id = heap_->GetAllocationId(value);
if (!allocation_id) {
auto id = heap_->AllocateValue(value);
auto [it, success] =
locals_.insert({value_node, heap_->arena().New<LValue>(Address(id))});
CARBON_CHECK(success) << "Duplicate definition of " << value_node.base();
} else {
auto [it, success] = locals_.insert(
{value_node, heap_->arena().New<LValue>(Address(*allocation_id))});
CARBON_CHECK(success) << "Duplicate definition of " << value_node.base();
}
}
void RuntimeScope::Initialize(ValueNodeView value_node,
Nonnull<const Value*> value) {
CARBON_CHECK(!value_node.constant_value().has_value());
@@ -98,16 +113,6 @@ auto RuntimeScope::Capture(
return result;
}
void RuntimeScope::TransitState() {
if (destructor_scope_ == State::Normal) {
destructor_scope_ = State::Destructor;
} else if (destructor_scope_ == State::Destructor) {
destructor_scope_ = State::CleanUpped;
} else {
destructor_scope_ = State::CleanUpped;
}
}
void Action::Print(llvm::raw_ostream& out) const {
switch (kind()) {
case Action::Kind::LValAction:
@@ -138,6 +143,9 @@ void Action::Print(llvm::raw_ostream& out) const {
case Action::Kind::CleanUpAction:
out << "clean up";
break;
case Action::Kind::DestroyAction:
out << "destroy";
break;
}
out << "." << pos_ << ".";
if (!results_.empty()) {
+41 -29
View File
@@ -27,12 +27,6 @@ namespace Carbon {
// not compile-time constants.
class RuntimeScope {
public:
enum class State {
Normal = 0,
Destructor = 1,
CleanUpped = 2,
};
// Returns a RuntimeScope whose Get() operation for a given name returns the
// storage owned by the first entry in `scopes` that defines that name. This
// behavior is closely analogous to a `[&]` capture in C++, hence the name.
@@ -42,8 +36,7 @@ class RuntimeScope {
-> RuntimeScope;
// Constructs a RuntimeScope that allocates storage in `heap`.
explicit RuntimeScope(Nonnull<HeapAllocationInterface*> heap)
: heap_(heap), destructor_scope_(State::Normal) {}
explicit RuntimeScope(Nonnull<HeapAllocationInterface*> heap) : heap_(heap) {}
// Moving a RuntimeScope transfers ownership of its allocations.
RuntimeScope(RuntimeScope&&) noexcept;
@@ -55,8 +48,12 @@ class RuntimeScope {
void Print(llvm::raw_ostream& out) const;
LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); }
// Binds `value` as the value of `value_node`.
void Bind(ValueNodeView value_node, Nonnull<const Value*> value);
// Allocates storage for `value_node` in `heap`, and initializes it with
// `value`.
// TODO: Update existing callers to use Bind instead, where appropriate.
void Initialize(ValueNodeView value_node, Nonnull<const Value*> value);
// Transfers the names and allocations from `other` into *this. The two
@@ -69,31 +66,16 @@ class RuntimeScope {
-> std::optional<Nonnull<const LValue*>>;
// Returns the local values in created order
auto locals() const -> std::vector<Nonnull<const LValue*>> {
std::vector<Nonnull<const LValue*>> res;
for (const auto& entry : locals_) {
res.push_back(entry.second);
}
return res;
auto allocations() const -> const std::vector<AllocationId>& {
return allocations_;
}
// Return scope state
// Normal = Scope is not bind at the top of a destructor call
// Destructor = Scope is bind to a destructor call and was not cleaned up,
// CleanedUp = Scope is bind to a destructor call and is cleaned up
auto DestructionState() const -> State { return destructor_scope_; }
// Transit the state from Normal to Destructor
// Transit the state from Destructor to CleanUpped
void TransitState();
private:
llvm::MapVector<ValueNodeView, Nonnull<const LValue*>,
std::map<ValueNodeView, unsigned>>
locals_;
std::vector<AllocationId> allocations_;
Nonnull<HeapAllocationInterface*> heap_;
State destructor_scope_;
};
// An Action represents the current state of a self-contained computation,
@@ -120,6 +102,7 @@ class Action {
ScopeAction,
RecursiveAction,
CleanUpAction,
DestroyAction
};
Action(const Value&) = delete;
@@ -291,21 +274,50 @@ class DeclarationAction : public Action {
Nonnull<const Declaration*> declaration_;
};
// An Action which implements destroying all local allocations in a scope.
class CleanupAction : public Action {
public:
explicit CleanupAction(RuntimeScope scope) : Action(Kind::CleanUpAction) {
locals_count_ = scope.locals().size();
explicit CleanupAction(RuntimeScope scope)
: Action(Kind::CleanUpAction),
allocations_count_(scope.allocations().size()) {
StartScope(std::move(scope));
}
auto locals_count() const -> int { return locals_count_; }
auto allocations_count() const -> int { return allocations_count_; }
static auto classof(const Action* action) -> bool {
return action->kind() == Kind::CleanUpAction;
}
private:
int locals_count_;
int allocations_count_;
};
// An Action which implements destroying a single value, including all nested
// values.
class DestroyAction : public Action {
public:
// 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
// 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,
Nonnull<const Value*> value)
: Action(Kind::DestroyAction), lvalue_(lvalue), value_(value) {}
static auto classof(const Action* action) -> bool {
return action->kind() == Kind::DestroyAction;
}
auto lvalue() const -> Nonnull<const LValue*> { return lvalue_; }
auto value() const -> Nonnull<const Value*> { return value_; }
private:
Nonnull<const LValue*> lvalue_;
Nonnull<const Value*> value_;
};
// Action which does nothing except introduce a new scope into the action
+2 -5
View File
@@ -152,6 +152,7 @@ static auto FinishActionKindFor(Action::Kind kind) -> FinishActionKind {
return FinishActionKind::NoValue;
case Action::Kind::ScopeAction:
case Action::Kind::CleanUpAction:
case Action::Kind::DestroyAction:
return FinishActionKind::NeverCalled;
}
}
@@ -313,11 +314,7 @@ void ActionStack::PopScopes(
while (!todo_.IsEmpty() && llvm::isa<ScopeAction>(*todo_.Top())) {
auto act = todo_.Pop();
if (act->scope()) {
if ((*act->scope()).DestructionState() <
RuntimeScope::State::CleanUpped) {
(*act->scope()).TransitState();
cleanup_stack.push(std::move(act));
}
cleanup_stack.push(std::move(act));
}
}
}
+12
View File
@@ -46,6 +46,18 @@ auto Heap::Write(const Address& a, Nonnull<const Value*> v,
return Success();
}
auto Heap::GetAllocationId(Nonnull<const Value*> v) const
-> std::optional<AllocationId> {
auto iter = std::find(values_.begin(), values_.end(), v);
if (iter != values_.end()) {
auto index = iter - values_.begin();
if (states_[index] == ValueState::Alive) {
return AllocationId(index);
}
}
return std::nullopt;
}
auto Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) const
-> ErrorOr<Success> {
if (states_[allocation.index_] == ValueState::Dead) {
+3
View File
@@ -41,6 +41,9 @@ class Heap : public HeapAllocationInterface {
auto Write(const Address& a, Nonnull<const Value*> v,
SourceLocation source_loc) -> ErrorOr<Success>;
auto GetAllocationId(Nonnull<const Value*> v) const
-> std::optional<AllocationId> override;
// Put the given value on the heap and mark its state.
// Mark UninitializedValue as uninitialized and other values as alive.
auto AllocateValue(Nonnull<const Value*> v) -> AllocationId override;
@@ -30,6 +30,11 @@ class HeapAllocationInterface {
// Returns the arena used to allocate the values in this heap.
virtual auto arena() const -> Arena& = 0;
// Returns the ID of the first allocation that holds `v`, if one exists.
// TODO: Find a way to remove this.
virtual auto GetAllocationId(Nonnull<const Value*> v) const
-> std::optional<AllocationId> = 0;
protected:
HeapAllocationInterface() = default;
virtual ~HeapAllocationInterface() = default;
+88 -38
View File
@@ -89,6 +89,9 @@ class Interpreter {
auto StepDeclaration() -> ErrorOr<Success>;
// State transition for object destruction.
auto StepCleanUp() -> ErrorOr<Success>;
auto StepDestroy() -> ErrorOr<Success>;
// State transition for tuple destruction.
auto StepCleanUpTuple() -> ErrorOr<Success>;
auto CreateStruct(const std::vector<FieldInitializer>& fields,
const std::vector<Nonnull<const Value*>>& values)
@@ -818,15 +821,17 @@ auto Interpreter::CallDestructor(Nonnull<const DestructorDeclaration*> fun,
CARBON_CHECK(method.is_method());
RuntimeScope method_scope(&heap_);
BindingMap generic_args;
CARBON_CHECK(PatternMatch(&method.me_pattern().value(), receiver,
fun->source_loc(), &method_scope, generic_args,
trace_stream_, this->arena_));
// TODO: move this logic into PatternMatch, and call it here.
auto p = &method.me_pattern().value();
const auto& placeholder = cast<BindingPlaceholderValue>(*p);
if (placeholder.value_node().has_value()) {
method_scope.Bind(*placeholder.value_node(), receiver);
}
CARBON_CHECK(method.body().has_value())
<< "Calling a method that's missing a body";
auto act = std::make_unique<StatementAction>(*method.body());
method_scope.TransitState();
return todo_.Spawn(std::unique_ptr<Action>(std::move(act)),
std::move(method_scope));
}
@@ -900,9 +905,18 @@ auto Interpreter::CallFunction(const CallExpression& call,
RuntimeScope method_scope(&heap_);
BindingMap generic_args;
// Bind the receiver to the `me` parameter.
CARBON_CHECK(PatternMatch(&method.me_pattern().value(), m.receiver(),
call.source_loc(), &method_scope, generic_args,
trace_stream_, this->arena_));
auto p = &method.me_pattern().value();
if (p->kind() == Value::Kind::BindingPlaceholderValue) {
// TODO: move this logic into PatternMatch
const auto& placeholder = cast<BindingPlaceholderValue>(*p);
if (placeholder.value_node().has_value()) {
method_scope.Bind(*placeholder.value_node(), m.receiver());
}
} else {
CARBON_CHECK(PatternMatch(&method.me_pattern().value(), m.receiver(),
call.source_loc(), &method_scope,
generic_args, trace_stream_, this->arena_));
}
// Bind the arguments to the parameters.
CARBON_CHECK(PatternMatch(&method.param_pattern().value(), converted_args,
call.source_loc(), &method_scope, generic_args,
@@ -1998,48 +2012,81 @@ auto Interpreter::StepDeclaration() -> ErrorOr<Success> {
}
}
auto Interpreter::StepCleanUp() -> 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();
auto& cleanup = cast<CleanupAction>(act);
if (act.pos() < cleanup.locals_count()) {
const auto* lvalue =
act.scope()->locals()[cleanup.locals_count() - act.pos() - 1];
SourceLocation source_loc("destructor", 1);
auto value = heap_.Read(lvalue->address(), source_loc);
if (value.ok()) {
if (act.scope()->DestructionState() < RuntimeScope::State::CleanUpped) {
if (const auto* class_obj = dyn_cast<NominalClassValue>(*value)) {
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);
}
}
} else {
if (const auto* class_obj = dyn_cast<NominalClassValue>(*value)) {
const auto& class_type = cast<NominalClassType>(class_obj->type());
const auto& class_dec = class_type.declaration();
const auto& class_members = class_dec.members();
for (const auto& member : class_members) {
if (const auto* var = dyn_cast<VariableDeclaration>(member)) {
const auto& type = var->static_type();
if (const auto* c_type = dyn_cast<NominalClassType>(&type)) {
const auto& c_dec = c_type->declaration();
if (c_dec.destructor().has_value()) {
Address object = lvalue->address();
Address mem = object.SubobjectAddress(Member(var));
auto v = heap_.Read(mem, source_loc);
act.scope()->TransitState();
return CallDestructor(*c_dec.destructor(), *v);
}
}
}
}
if (item->kind() == Value::Kind::TupleValue) {
return todo_.Spawn(
std::make_unique<DestroyAction>(destroy_act.lvalue(), item));
}
act.scope()->TransitState();
// 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];
if (const auto* var = dyn_cast<VariableDeclaration>(member)) {
Address object = destroy_act.lvalue()->address();
Address mem = object.SubobjectAddress(Member(var));
SourceLocation source_loc("destructor", 1);
auto v = heap_.Read(mem, source_loc);
return todo_.Spawn(
std::make_unique<DestroyAction>(destroy_act.lvalue(), *v));
}
}
}
}
todo_.Pop();
return Success();
}
auto Interpreter::StepCleanUp() -> ErrorOr<Success> {
Action& act = todo_.CurrentAction();
CleanupAction& cleanup = cast<CleanupAction>(act);
if (act.pos() < cleanup.allocations_count()) {
auto allocation =
act.scope()->allocations()[cleanup.allocations_count() - act.pos() - 1];
auto lvalue = arena_->New<LValue>(Address(allocation));
SourceLocation source_loc("destructor", 1);
auto value = heap_.Read(lvalue->address(), source_loc);
// Step over uninitialized values
if (value.ok()) {
return todo_.Spawn(std::make_unique<DestroyAction>(lvalue, *value));
}
}
todo_.Pop();
return Success();
}
@@ -2069,6 +2116,9 @@ auto Interpreter::Step() -> ErrorOr<Success> {
case Action::Kind::CleanUpAction:
CARBON_RETURN_IF_ERROR(StepCleanUp());
break;
case Action::Kind::DestroyAction:
CARBON_RETURN_IF_ERROR(StepDestroy());
break;
case Action::Kind::ScopeAction:
CARBON_FATAL() << "ScopeAction escaped ActionStack";
case Action::Kind::RecursiveAction:
@@ -9,6 +9,7 @@
// CHECK:STDOUT: DESTRUCTOR B 2
// CHECK:STDOUT: DESTRUCTOR A 3
// CHECK:STDOUT: DESTRUCTOR A 4
// CHECK:STDOUT: DESTRUCTOR A 5
// CHECK:STDOUT: result: 1
package ExplorerTest api;
@@ -28,15 +29,16 @@ class B{
Print("DESTRUCTOR B {0}",me.n);
}
fn Create(x: i32) -> B{
return {.n = x, .a = A.Create(3) };
return {.n = x, .a1 = A.Create(4),.a2 = A.Create(3) };
}
var a1: A;
var n: i32;
var a: A;
var a2: A;
}
fn Main() -> i32 {
var a: A = A.Create(4);
var a: A = A.Create(5);
var b: B = B.Create(2);
var c: A = A.Create(1);
return 1;
+30
View File
@@ -0,0 +1,30 @@
// 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 A 1
// CHECK:STDOUT: DESTRUCTOR A 2
// CHECK:STDOUT: DESTRUCTOR A 3
// CHECK:STDOUT: DESTRUCTOR A 4
// CHECK:STDOUT: DESTRUCTOR A 5
// CHECK:STDOUT: DESTRUCTOR A 6
// CHECK:STDOUT: result: 1
package ExplorerTest api;
class A{
destructor[me: Self]{
Print("DESTRUCTOR A {0}",me.n);
}
var n: i32;
}
fn Main() -> i32 {
var a: [A; 2] = ({.n = 6},{.n = 5});
var b: [[A; 2]; 2] = (({.n = 4},{.n = 3}), ({.n = 2},{.n = 1}));
return 1;
}
@@ -0,0 +1,27 @@
// 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 A 0
// CHECK:STDOUT: DESTRUCTOR A 1
// CHECK:STDOUT: DESTRUCTOR A 2
// CHECK:STDOUT: result: 1
package ExplorerTest api;
class A{
destructor[me: Self]{
Print("DESTRUCTOR A {0}",me.n);
}
var n: i32;
}
fn Main() -> i32 {
var a1: A = {.n = 2};
var a: [A; 2] = ({.n = 1},{.n = 0});
return 1;
}
@@ -0,0 +1,38 @@
// 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 A 1
// CHECK:STDOUT: DESTRUCTOR B 2
// CHECK:STDOUT: result: 1
package ExplorerTest api;
class A{
class B{
destructor[me: Self]{
Print("DESTRUCTOR B {0}",me.n);
}
fn Create(x: i32) -> B{
return {.n = x };
}
var n: i32;
}
destructor[me: Self]{
Print("DESTRUCTOR A {0}",me.n);
}
fn Create(x: i32) -> A{
return {.n = x, .b = B.Create(2)};
}
var n: i32;
var b : B;
}
fn Main() -> i32 {
var a: A = A.Create(1);
return 1;
}
+38
View File
@@ -0,0 +1,38 @@
// 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}
// AUTOUPDATE: %{explorer} %s
// CHECK:STDOUT: TEST
// CHECK:STDOUT: TEST 2
// CHECK:STDOUT: DESTRUCTOR A 1
// CHECK:STDOUT: result: 1
package ExplorerTest api;
class B{
var n : i32;
}
class A{
destructor[me: Self]{
Print("DESTRUCTOR A {0}",me.n);
}
fn test[me: Self](){
Print("TEST");
}
fn test2[me: Self](){
Print("TEST 2");
}
var n: i32;
}
fn Main() -> i32 {
var a: A = {.n = 1 };
a.test();
a.test2();
return 1;
}