From d02366f8817e3bd34be3476da7565faa497221bc Mon Sep 17 00:00:00 2001 From: Adrien Leravat Date: Tue, 18 Jul 2023 01:26:01 +0200 Subject: [PATCH] Explorer: Prevent copies when initializing a let binding from reference expression (#2946) Prevent copies when initializing value expression from reference expression. This is based on https://github.com/carbon-language/carbon-lang/pull/2006, which introduces expression categories, and how it is possible to convert to/from those different categories. Continuation of https://github.com/carbon-language/carbon-lang/pull/2907 ## Functional changes * Initializing a value expression from a reference expression takes its value without a copy * Reading from the value expression causes an error if the value changed from the time it was initialized * In this situation, prevents a copy both for variable definitions, and call parameter bindings ## Main implementation changes * Add new `ExpressionCategoryAction`, which evaluates an expression and returns an `ExpressionValue` containing its category and address (if any), in addition to the resulting `Value*` * `ExpressionAction`s now invokes `ExpressionCategoryAction` and unwraps the returned `ExpressionValue` * `RuntimeScope::BindAndPin` method, and corresponding when attempting to read a `value_node`. ## Next work * Avoid unnecessary copies from value expression to value expression, after ensuring that even value expression temporaries are registered for destruction (https://github.com/Pixep/carbon-lang/pull/9) --- explorer/ast/element_path.h | 1 + explorer/ast/value.cpp | 7 + explorer/ast/value.h | 90 +++--- explorer/ast/value_kinds.def | 1 + explorer/ast/value_transform.h | 3 + explorer/interpreter/BUILD | 5 + explorer/interpreter/action.cpp | 44 ++- explorer/interpreter/action.h | 66 ++++- explorer/interpreter/action_stack.cpp | 19 +- explorer/interpreter/heap.cpp | 73 ++++- explorer/interpreter/heap.h | 28 +- .../interpreter/heap_allocation_interface.h | 23 +- explorer/interpreter/interpreter.cpp | 269 ++++++++++++------ explorer/interpreter/type_checker.cpp | 6 + explorer/interpreter/type_structure.cpp | 2 + .../let/fail_pinned_value_changed.carbon | 16 ++ .../let/fail_pinned_value_restored.carbon | 18 ++ ...fail_pinned_value_subobject_changed.carbon | 24 ++ .../testdata/let/pinned_value_copied.carbon | 27 ++ .../let/pinned_value_multiple_times.carbon | 17 ++ .../pinned_value_mutation_unread_data.carbon | 21 ++ .../let/pinned_value_mutation_unused.carbon | 16 ++ .../value_expr_binding_from_reference.carbon | 3 +- ...arbon => value_expr_from_reference.carbon} | 3 +- .../value_expr_from_reference_deref.carbon | 32 +++ ...value_expr_from_reference_subobject.carbon | 39 +++ ...n => reference_expr_from_reference.carbon} | 0 explorer/trace_testdata/full_trace.carbon | 82 ++++-- 28 files changed, 757 insertions(+), 178 deletions(-) create mode 100644 explorer/testdata/let/fail_pinned_value_changed.carbon create mode 100644 explorer/testdata/let/fail_pinned_value_restored.carbon create mode 100644 explorer/testdata/let/fail_pinned_value_subobject_changed.carbon create mode 100644 explorer/testdata/let/pinned_value_copied.carbon create mode 100644 explorer/testdata/let/pinned_value_multiple_times.carbon create mode 100644 explorer/testdata/let/pinned_value_mutation_unread_data.carbon create mode 100644 explorer/testdata/let/pinned_value_mutation_unused.carbon rename explorer/testdata/let/{value_expr_from_ref.carbon => value_expr_from_reference.carbon} (90%) create mode 100644 explorer/testdata/let/value_expr_from_reference_deref.carbon create mode 100644 explorer/testdata/let/value_expr_from_reference_subobject.carbon rename explorer/testdata/var/local/{reference_expr_from_ref.carbon => reference_expr_from_reference.carbon} (100%) diff --git a/explorer/ast/element_path.h b/explorer/ast/element_path.h index 79c6bb995478..74026f40a3e4 100644 --- a/explorer/ast/element_path.h +++ b/explorer/ast/element_path.h @@ -115,6 +115,7 @@ class ElementPath { // another Value, so its implementation details are tied to the implementation // details of Value. friend class Value; + friend class Heap; std::vector components_; }; diff --git a/explorer/ast/value.cpp b/explorer/ast/value.cpp index 3c81f7087d65..38f53a6275d3 100644 --- a/explorer/ast/value.cpp +++ b/explorer/ast/value.cpp @@ -91,6 +91,7 @@ struct NestedValueVisitor { auto Visit(ValueNodeView) -> bool { return true; } auto Visit(int) -> bool { return true; } auto Visit(Address) -> bool { return true; } + auto Visit(ExpressionCategory) -> bool { return true; } auto Visit(const std::string&) -> bool { return true; } auto Visit(Nonnull) -> bool { // This is the pointer to the most-derived value within a class value, @@ -567,6 +568,10 @@ void Value::Print(llvm::raw_ostream& out) const { case Value::Kind::LocationValue: out << "lval<" << cast(*this).address() << ">"; break; + case Value::Kind::ReferenceExpressionValue: + out << "ref_expr<" << cast(*this).address() + << ">"; + break; case Value::Kind::BoolType: out << "bool"; break; @@ -997,6 +1002,7 @@ auto TypeEqual(Nonnull t1, Nonnull t2, case Value::Kind::StringValue: case Value::Kind::PointerValue: case Value::Kind::LocationValue: + case Value::Kind::ReferenceExpressionValue: case Value::Kind::BindingPlaceholderValue: case Value::Kind::AddrValue: case Value::Kind::UninitializedValue: @@ -1148,6 +1154,7 @@ auto ValueStructurallyEqual( case Value::Kind::AlternativeConstructorValue: case Value::Kind::PointerValue: case Value::Kind::LocationValue: + case Value::Kind::ReferenceExpressionValue: case Value::Kind::UninitializedValue: case Value::Kind::MemberName: // TODO: support pointer comparisons once we have a clearer distinction diff --git a/explorer/ast/value.h b/explorer/ast/value.h index 010c26075ff5..c3c23f61b930 100644 --- a/explorer/ast/value.h +++ b/explorer/ast/value.h @@ -96,38 +96,6 @@ class Value { const Kind kind_; }; -// Contains the result of the evaluation of an expression, including a value, -// the original expression category, and an optional address if available. -class ExpressionResult { - public: - static auto Value(Nonnull v) -> ExpressionResult { - return ExpressionResult(v, std::nullopt, ExpressionCategory::Value); - } - static auto Reference(Nonnull v, Address address) - -> ExpressionResult { - return ExpressionResult(v, std::move(address), - ExpressionCategory::Reference); - } - static auto Initializing(Nonnull v, Address address) - -> ExpressionResult { - return ExpressionResult(v, std::move(address), - ExpressionCategory::Initializing); - } - - ExpressionResult(Nonnull v, - std::optional
address, ExpressionCategory cat) - : value_(v), address_(std::move(address)), expr_cat_(cat) {} - - auto value() const -> Nonnull { return value_; } - auto address() const -> const std::optional
& { return address_; } - auto expression_category() const -> ExpressionCategory { return expr_cat_; } - - private: - Nonnull value_; - std::optional
address_; - ExpressionCategory expr_cat_; -}; - // Returns whether the fully-resolved kind that this value will eventually have // is currently unknown, because it depends on a generic parameter. inline auto IsValueKindDependent(Nonnull type) -> bool { @@ -296,6 +264,64 @@ class LocationValue : public Value { Address value_; }; +// Contains the result of the evaluation of an expression, including a value, +// the original expression category, and an optional address if available. +class ExpressionResult { + public: + static auto Value(Nonnull v) -> ExpressionResult { + return ExpressionResult(v, std::nullopt, ExpressionCategory::Value); + } + static auto Reference(Nonnull v, Address address) + -> ExpressionResult { + return ExpressionResult(v, std::move(address), + ExpressionCategory::Reference); + } + static auto Initializing(Nonnull v, Address address) + -> ExpressionResult { + return ExpressionResult(v, std::move(address), + ExpressionCategory::Initializing); + } + + ExpressionResult(Nonnull v, + std::optional
address, ExpressionCategory cat) + : value_(v), address_(std::move(address)), expr_cat_(cat) {} + + auto value() const -> Nonnull { return value_; } + auto address() const -> const std::optional
& { return address_; } + auto expression_category() const -> ExpressionCategory { return expr_cat_; } + + private: + Nonnull value_; + std::optional
address_; + ExpressionCategory expr_cat_; +}; + +// Represents the result of the evaluation of a reference expression, and +// holds the resulting `Value*` and its `Address`. +class ReferenceExpressionValue : public Value { + public: + ReferenceExpressionValue(Nonnull value, Address address) + : Value(Kind::ReferenceExpressionValue), + value_(value), + address_(std::move(address)) {} + + static auto classof(const Value* value) -> bool { + return value->kind() == Kind::ReferenceExpressionValue; + } + + template + auto Decompose(F f) const { + return f(value_, address_); + } + + auto value() const -> Nonnull { return value_; } + auto address() const -> const Address& { return address_; } + + private: + Nonnull value_; + Address address_; +}; + // A pointer value class PointerValue : public Value { public: diff --git a/explorer/ast/value_kinds.def b/explorer/ast/value_kinds.def index 11ca5c5aba5d..2d0564e43fcd 100644 --- a/explorer/ast/value_kinds.def +++ b/explorer/ast/value_kinds.def @@ -16,6 +16,7 @@ CARBON_VALUE_KIND(DestructorValue) CARBON_VALUE_KIND(BoundMethodValue) CARBON_VALUE_KIND(PointerValue) CARBON_VALUE_KIND(LocationValue) +CARBON_VALUE_KIND(ReferenceExpressionValue) CARBON_VALUE_KIND(BoolValue) CARBON_VALUE_KIND(StructValue) CARBON_VALUE_KIND(NominalClassValue) diff --git a/explorer/ast/value_transform.h b/explorer/ast/value_transform.h index 4e49f296562d..d13635f0ce59 100644 --- a/explorer/ast/value_transform.h +++ b/explorer/ast/value_transform.h @@ -6,6 +6,7 @@ #define CARBON_EXPLORER_AST_VALUE_TRANSFORM_H_ #include "common/error.h" +#include "explorer/ast/expression_category.h" #include "explorer/ast/value.h" namespace Carbon { @@ -238,6 +239,8 @@ class ValueTransform : public TransformBase { auto operator()(Address addr) -> Address { return addr; } + auto operator()(ExpressionCategory cat) -> ExpressionCategory { return cat; } + auto operator()(ValueNodeView value_node) -> ValueNodeView { return value_node; } diff --git a/explorer/interpreter/BUILD b/explorer/interpreter/BUILD index 6c9931c1161d..94102e9ec057 100644 --- a/explorer/interpreter/BUILD +++ b/explorer/interpreter/BUILD @@ -23,6 +23,7 @@ cc_library( "//explorer/common:arena", "//explorer/common:error_builders", "//explorer/common:nonnull", + "//explorer/common:source_location", "@llvm-project//llvm:Support", ], ) @@ -80,6 +81,7 @@ cc_library( ":action", ":heap_allocation_interface", "//common:check", + "//common:error", "//common:ostream", "//explorer/ast", "//explorer/common:error_builders", @@ -93,9 +95,11 @@ cc_library( name = "heap_allocation_interface", hdrs = ["heap_allocation_interface.h"], deps = [ + "//common:error", "//explorer/ast", "//explorer/common:arena", "//explorer/common:nonnull", + "//explorer/common:source_location", ], ) @@ -254,6 +258,7 @@ cc_library( deps = [ "//common:ostream", "//explorer/ast", + "//explorer/ast:expression_category", "//explorer/common:nonnull", "@llvm-project//llvm:Support", ], diff --git a/explorer/interpreter/action.cpp b/explorer/interpreter/action.cpp index 7110f04f2363..795252513152 100644 --- a/explorer/interpreter/action.cpp +++ b/explorer/interpreter/action.cpp @@ -11,10 +11,12 @@ #include #include "common/check.h" +#include "common/error.h" #include "explorer/ast/declaration.h" #include "explorer/ast/expression.h" #include "explorer/ast/value.h" #include "explorer/common/arena.h" +#include "explorer/common/source_location.h" #include "explorer/interpreter/stack.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" @@ -25,12 +27,14 @@ using llvm::cast; RuntimeScope::RuntimeScope(RuntimeScope&& other) noexcept : locals_(std::move(other.locals_)), + bound_values_(std::move(other.bound_values_)), // To transfer ownership of other.allocations_, we have to empty it out. allocations_(std::exchange(other.allocations_, {})), heap_(other.heap_) {} auto RuntimeScope::operator=(RuntimeScope&& rhs) noexcept -> RuntimeScope& { locals_ = std::move(rhs.locals_); + bound_values_ = std::move(rhs.bound_values_); // To transfer ownership of rhs.allocations_, we have to empty it out. allocations_ = std::exchange(rhs.allocations_, {}); heap_ = rhs.heap_; @@ -54,6 +58,13 @@ void RuntimeScope::Bind(ValueNodeView value_node, Address address) { CARBON_CHECK(success) << "Duplicate definition of " << value_node.base(); } +void RuntimeScope::BindAndPin(ValueNodeView value_node, Address address) { + Bind(value_node, address); + bool success = bound_values_.insert(&value_node.base()).second; + CARBON_CHECK(success) << "Duplicate pinned node for " << value_node.base(); + heap_->BindValueToReference(value_node, address); +} + void RuntimeScope::BindLifetimeToScope(Address address) { CARBON_CHECK(address.element_path_.IsEmpty()) << "Cannot extend lifetime of a specific sub-element"; @@ -84,23 +95,35 @@ auto RuntimeScope::Initialize(ValueNodeView value_node, void RuntimeScope::Merge(RuntimeScope other) { CARBON_CHECK(heap_ == other.heap_); for (auto& element : other.locals_) { - CARBON_CHECK(locals_.count(element.first) == 0) - << "Duplicate definition of" << element.first; - locals_.insert(element); + bool success = locals_.insert(element).second; + CARBON_CHECK(success) << "Duplicate definition of " << element.first; + } + for (const auto* element : other.bound_values_) { + bool success = bound_values_.insert(element).second; + CARBON_CHECK(success) << "Duplicate bound value."; } allocations_.insert(allocations_.end(), other.allocations_.begin(), other.allocations_.end()); other.allocations_.clear(); } -auto RuntimeScope::Get(ValueNodeView value_node) const - -> std::optional> { +auto RuntimeScope::Get(ValueNodeView value_node, + SourceLocation source_loc) const + -> ErrorOr>> { auto it = locals_.find(value_node); - if (it != locals_.end()) { - return it->second; - } else { - return std::nullopt; + if (it == locals_.end()) { + return {std::nullopt}; } + if (bound_values_.contains(&value_node.base())) { + // Check if the bound value is still alive. + CARBON_CHECK(it->second->kind() == Value::Kind::LocationValue); + if (!heap_->is_bound_value_alive( + value_node, cast(it->second)->address())) { + return ProgramError(source_loc) + << "Reference has changed since this value was bound."; + } + } + return {it->second}; } auto RuntimeScope::Capture( @@ -122,6 +145,9 @@ void Action::Print(llvm::raw_ostream& out) const { case Action::Kind::LocationAction: out << cast(*this).expression() << " "; break; + case Action::Kind::ValueExpressionAction: + out << cast(*this).expression() << " "; + break; case Action::Kind::ExpressionAction: out << cast(*this).expression() << " "; break; diff --git a/explorer/interpreter/action.h b/explorer/interpreter/action.h index 7772d4313112..13447b7cee19 100644 --- a/explorer/interpreter/action.h +++ b/explorer/interpreter/action.h @@ -18,9 +18,11 @@ #include "explorer/ast/pattern.h" #include "explorer/ast/statement.h" #include "explorer/ast/value.h" +#include "explorer/common/source_location.h" #include "explorer/interpreter/dictionary.h" #include "explorer/interpreter/heap_allocation_interface.h" #include "explorer/interpreter/stack.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/MapVector.h" #include "llvm/Support/Compiler.h" @@ -61,6 +63,10 @@ class RuntimeScope { // allocating local storage. void Bind(ValueNodeView value_node, Address address); + // Binds location `address` of a reference value to `value_node` without + // allocating local storage, and pins the value, making it immutable. + void BindAndPin(ValueNodeView value_node, Address address); + // Binds unlocated `value` to `value_node` without allocating local storage. // TODO: BindValue should pin the lifetime of `value` and make sure it isn't // mutated. @@ -74,8 +80,8 @@ class RuntimeScope { // - its `LocationValue*` if bound to a reference expression in this scope, // - a `Value*` if bound to a value expression in this scope, or // - `nullptr` if not bound. - auto Get(ValueNodeView value_node) const - -> std::optional>; + auto Get(ValueNodeView value_node, SourceLocation source_loc) const + -> ErrorOr>>; // Returns the local values with allocation in created order. auto allocations() const -> const std::vector& { @@ -86,6 +92,7 @@ class RuntimeScope { llvm::MapVector, std::map> locals_; + llvm::DenseSet bound_values_; std::vector allocations_; Nonnull heap_; }; @@ -106,6 +113,7 @@ class Action { public: enum class Kind { LocationAction, + ValueExpressionAction, ExpressionAction, WitnessAction, StatementAction, @@ -165,7 +173,9 @@ class Action { scope_ = std::move(scope); } - auto source_loc() -> std::optional { return source_loc_; } + auto source_loc() const -> std::optional { + return source_loc_; + } protected: // Constructs an Action. `kind` must be the enumerator corresponding to the @@ -202,17 +212,17 @@ class LocationAction : public Action { }; // An Action which implements evaluation of an Expression to produce a `Value*`. -class ExpressionAction : public Action { +class ValueExpressionAction : public Action { public: - explicit ExpressionAction( + explicit ValueExpressionAction( Nonnull expression, std::optional initialized_location = std::nullopt) - : Action(expression->source_loc(), Kind::ExpressionAction), + : Action(expression->source_loc(), Kind::ValueExpressionAction), expression_(expression), location_received_(initialized_location) {} static auto classof(const Action* action) -> bool { - return action->kind() == Kind::ExpressionAction; + return action->kind() == Kind::ValueExpressionAction; } // The Expression this Action evaluates. @@ -228,6 +238,44 @@ class ExpressionAction : public Action { std::optional location_received_; }; +// An Action which implements evaluation of a reference Expression to produce an +// `ReferenceExpressionValue*`. The `preserve_nested_categories` flag can be +// used to preserve values as `ReferenceExpressionValue` in nested value types, +// such as tuples. +class ExpressionAction : public Action { + public: + ExpressionAction( + Nonnull expression, bool preserve_nested_categories, + std::optional initialized_location = std::nullopt) + : Action(expression->source_loc(), Kind::ExpressionAction), + expression_(expression), + location_received_(initialized_location), + preserve_nested_categories_(preserve_nested_categories) {} + + static auto classof(const Action* action) -> bool { + return action->kind() == Kind::ExpressionAction; + } + + // The Expression this Action evaluates. + auto expression() const -> const Expression& { return *expression_; } + + // Returns whether direct descendent actions should preserve values as + // `ReferenceExpressionValue*`s. + auto preserve_nested_categories() const -> bool { + return preserve_nested_categories_; + } + + // The location provided for the initializing expression, if any. + auto location_received() const -> std::optional { + return location_received_; + } + + private: + Nonnull expression_; + std::optional location_received_; + bool preserve_nested_categories_; +}; + // An Action which implements the Instantiation of Type. The result is expressed // as a Value. class TypeInstantiationAction : public Action { @@ -334,8 +382,8 @@ class DeclarationAction : public Action { // An Action which implements destroying all local allocations in a scope. class CleanUpAction : public Action { public: - explicit CleanUpAction(RuntimeScope scope) - : Action(std::nullopt, Kind::CleanUpAction), + explicit CleanUpAction(RuntimeScope scope, SourceLocation source_loc) + : Action(source_loc, Kind::CleanUpAction), allocations_count_(scope.allocations().size()) { StartScope(std::move(scope)); } diff --git a/explorer/interpreter/action_stack.cpp b/explorer/interpreter/action_stack.cpp index 31cec81ea20b..b40fcd693b0d 100644 --- a/explorer/interpreter/action_stack.cpp +++ b/explorer/interpreter/action_stack.cpp @@ -49,15 +49,15 @@ auto ActionStack::ValueOfNode(ValueNodeView value_node, // with that node. This will help keep unwanted dynamic-scoping behavior // from sneaking in. if (action->scope().has_value()) { - std::optional> result = - action->scope()->Get(value_node); + CARBON_ASSIGN_OR_RETURN(auto result, + action->scope()->Get(value_node, source_loc)); if (result.has_value()) { return *result; } } } if (globals_.has_value()) { - std::optional> result = globals_->Get(value_node); + CARBON_ASSIGN_OR_RETURN(auto result, globals_->Get(value_node, source_loc)); if (result.has_value()) { return *result; } @@ -110,6 +110,7 @@ enum class FinishActionKind { static auto FinishActionKindFor(Action::Kind kind) -> FinishActionKind { switch (kind) { + case Action::Kind::ValueExpressionAction: case Action::Kind::ExpressionAction: case Action::Kind::WitnessAction: case Action::Kind::LocationAction: @@ -207,8 +208,8 @@ auto ActionStack::UnwindToWithCaptureScopesToDestroy( auto item = todo_.Pop(); auto& scope = item->scope(); if (scope && item->kind() != Action::Kind::CleanUpAction) { - std::unique_ptr cleanup_action = - std::make_unique(std::move(*scope)); + std::unique_ptr cleanup_action = std::make_unique( + std::move(*scope), ast_node->source_loc()); scopes_to_destroy.push(std::move(cleanup_action)); } } @@ -274,8 +275,8 @@ void ActionStack::PushCleanUpActions( while (!actions.empty()) { auto& act = actions.top(); if (act->scope()) { - std::unique_ptr cleanup_action = - std::make_unique(std::move(*act->scope())); + std::unique_ptr cleanup_action = std::make_unique( + std::move(*act->scope()), SourceLocation("stack cleanup", 1)); todo_.Push(std::move(cleanup_action)); } actions.pop(); @@ -285,8 +286,8 @@ void ActionStack::PushCleanUpActions( void ActionStack::PushCleanUpAction(std::unique_ptr act) { auto& scope = act->scope(); if (scope && act->kind() != Action::Kind::CleanUpAction) { - std::unique_ptr cleanup_action = - std::make_unique(std::move(*scope)); + std::unique_ptr cleanup_action = std::make_unique( + std::move(*scope), SourceLocation("stack cleanup", 1)); todo_.Push(std::move(cleanup_action)); } } diff --git a/explorer/interpreter/heap.cpp b/explorer/interpreter/heap.cpp index 6b7673f8161a..4abcdf191104 100644 --- a/explorer/interpreter/heap.cpp +++ b/explorer/interpreter/heap.cpp @@ -5,8 +5,10 @@ #include "explorer/interpreter/heap.h" #include "common/check.h" +#include "common/error.h" #include "explorer/ast/value.h" #include "explorer/common/error_builders.h" +#include "explorer/common/source_location.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Error.h" @@ -24,6 +26,7 @@ auto Heap::AllocateValue(Nonnull v) -> AllocationId { } else { states_.push_back(ValueState::Alive); } + bound_values_.push_back(llvm::DenseMap{}); return a; } @@ -49,13 +52,25 @@ auto Heap::Write(const Address& a, Nonnull v, CARBON_ASSIGN_OR_RETURN(values_[a.allocation_.index_], values_[a.allocation_.index_]->SetField( arena_, a.element_path_, v, source_loc)); + auto& bound_values_map = bound_values_[a.allocation_.index_]; + // End lifetime of all values bound to this address and its subobjects. + if (a.element_path_.IsEmpty()) { + bound_values_map.clear(); + } else { + for (auto value_it = bound_values_map.begin(); + value_it != bound_values_map.end(); ++value_it) { + if (AddressesAreStrictlyNested(a, value_it->second)) { + bound_values_map.erase(value_it); + } + } + } return Success(); } auto Heap::CheckAlive(AllocationId allocation, SourceLocation source_loc) const -> ErrorOr { - if (states_[allocation.index_] == ValueState::Dead || - states_[allocation.index_] == ValueState::Discarded) { + const auto state = states_[allocation.index_]; + if (state == ValueState::Dead || state == ValueState::Discarded) { return ProgramError(source_loc) << "undefined behavior: access to dead or discarded value " << *values_[allocation.index_]; @@ -73,16 +88,19 @@ auto Heap::CheckInit(AllocationId allocation, SourceLocation source_loc) const return Success(); } -void Heap::Deallocate(AllocationId allocation) { +auto Heap::Deallocate(AllocationId allocation) -> ErrorOr { if (states_[allocation.index_] != ValueState::Dead) { states_[allocation.index_] = ValueState::Dead; } else { CARBON_FATAL() << "deallocating an already dead value: " << *values_[allocation.index_]; } + return Success(); } -void Heap::Deallocate(const Address& a) { Deallocate(a.allocation_); } +auto Heap::Deallocate(const Address& a) -> ErrorOr { + return Deallocate(a.allocation_); +} auto Heap::is_initialized(AllocationId allocation) const -> bool { return states_[allocation.index_] != ValueState::Uninitialized; @@ -97,6 +115,16 @@ void Heap::Discard(AllocationId allocation) { states_[allocation.index_] = ValueState::Discarded; } +void Heap::BindValueToReference(const ValueNodeView& node, const Address& a) { + // Update mapped node ignoring any previous mapping. + bound_values_[a.allocation_.index_].insert({&node.base(), a}); +} + +auto Heap::is_bound_value_alive(const ValueNodeView& node, + const Address& a) const -> bool { + return bound_values_[a.allocation_.index_].contains(&node.base()); +} + void Heap::Print(llvm::raw_ostream& out) const { llvm::ListSeparator sep; for (size_t i = 0; i < values_.size(); ++i) { @@ -111,4 +139,41 @@ void Heap::Print(llvm::raw_ostream& out) const { } } +auto Heap::AddressesAreStrictlyNested(const Address& first, + const Address& second) -> bool { + if (first.allocation_.index_ != second.allocation_.index_) { + return false; + } + return PathsAreStrictlyNested(first.element_path_, second.element_path_); +} + +auto Heap::PathsAreStrictlyNested(const ElementPath& first, + const ElementPath& second) -> bool { + for (size_t i = 0; + i < std::min(first.components_.size(), second.components_.size()); ++i) { + Nonnull element = first.components_[i].element(); + Nonnull other_element = second.components_[i].element(); + if (element->kind() != other_element->kind()) { + return false; + } + switch (element->kind()) { + case Carbon::ElementKind::NamedElement: + if (!element->IsNamed( + llvm::cast(other_element)->name())) { + return false; + } + break; + case Carbon::ElementKind::PositionalElement: + if (llvm::cast(element)->index() != + llvm::cast(other_element)->index()) { + return false; + } + break; + case Carbon::ElementKind::BaseElement: + // Nothing to test. + break; + } + } + return true; +} } // namespace Carbon diff --git a/explorer/interpreter/heap.h b/explorer/interpreter/heap.h index fb67092e410d..20b975db60dd 100644 --- a/explorer/interpreter/heap.h +++ b/explorer/interpreter/heap.h @@ -10,6 +10,7 @@ #include "common/ostream.h" #include "explorer/ast/address.h" #include "explorer/ast/value.h" +#include "explorer/ast/value_node.h" #include "explorer/common/nonnull.h" #include "explorer/common/source_location.h" #include "explorer/interpreter/heap_allocation_interface.h" @@ -35,20 +36,27 @@ class Heap : public HeapAllocationInterface { // Returns the value at the given address in the heap after // checking that it is alive. auto Read(const Address& a, SourceLocation source_loc) const - -> ErrorOr>; + -> ErrorOr> override; // Writes the given value at the address in the heap after // checking that the address is alive. auto Write(const Address& a, Nonnull v, - SourceLocation source_loc) -> ErrorOr; + SourceLocation source_loc) -> ErrorOr override; + + // Returns whether the value bound at the given node is still alive. + auto is_bound_value_alive(const ValueNodeView& node, const Address& a) const + -> bool override; + + void BindValueToReference(const ValueNodeView& node, + const Address& a) override; // Put the given value on the heap and mark its state. // Mark UninitializedValue as uninitialized and other values as alive. auto AllocateValue(Nonnull v) -> AllocationId override; // Marks this allocation, and all of its sub-objects, as dead. - void Deallocate(AllocationId allocation) override; - void Deallocate(const Address& a); + auto Deallocate(AllocationId allocation) -> ErrorOr override; + auto Deallocate(const Address& a) -> ErrorOr; // Marks this allocation, and all its sub-objects, as discarded. void Discard(AllocationId allocation); @@ -65,6 +73,17 @@ class Heap : public HeapAllocationInterface { auto arena() const -> Arena& override { return *arena_; } private: + // Returns whether the address have the same AllocationdId and their path + // are strictly nested. + static auto AddressesAreStrictlyNested(const Address& first, + const Address& second) -> bool; + + // Returns whether the provided paths are strictly nested. This checks the + // name, index, and base element only, and might not valid if used to + // compare paths based on a different AllocationId. + static auto PathsAreStrictlyNested(const ElementPath& first, + const ElementPath& second) -> bool; + // Signal an error if the allocation is no longer alive. auto CheckAlive(AllocationId allocation, SourceLocation source_loc) const -> ErrorOr; @@ -76,6 +95,7 @@ class Heap : public HeapAllocationInterface { Nonnull arena_; std::vector> values_; std::vector states_; + std::vector> bound_values_; }; } // namespace Carbon diff --git a/explorer/interpreter/heap_allocation_interface.h b/explorer/interpreter/heap_allocation_interface.h index dc67c1432580..84ece4ca9ed2 100644 --- a/explorer/interpreter/heap_allocation_interface.h +++ b/explorer/interpreter/heap_allocation_interface.h @@ -5,9 +5,12 @@ #ifndef CARBON_EXPLORER_INTERPRETER_HEAP_ALLOCATION_INTERFACE_H_ #define CARBON_EXPLORER_INTERPRETER_HEAP_ALLOCATION_INTERFACE_H_ +#include "common/error.h" #include "explorer/ast/address.h" +#include "explorer/ast/value_node.h" #include "explorer/common/arena.h" #include "explorer/common/nonnull.h" +#include "explorer/common/source_location.h" namespace Carbon { @@ -21,15 +24,33 @@ class HeapAllocationInterface { auto operator=(const HeapAllocationInterface&) -> HeapAllocationInterface& = delete; + // Returns the value at the given address in the heap after + // checking that it is alive. + virtual auto Read(const Address& a, SourceLocation source_loc) const + -> ErrorOr> = 0; + + // Writes the given value at the address in the heap after + // checking that the address is alive. + virtual auto Write(const Address& a, Nonnull v, + SourceLocation source_loc) -> ErrorOr = 0; + // Put the given value on the heap and mark it as alive. virtual auto AllocateValue(Nonnull v) -> AllocationId = 0; // Marks this allocation, and all of its sub-objects, as dead. - virtual void Deallocate(AllocationId allocation) = 0; + virtual auto Deallocate(AllocationId allocation) -> ErrorOr = 0; // Returns the arena used to allocate the values in this heap. virtual auto arena() const -> Arena& = 0; + // Binds a value node to a reference, and manages its lifetime. + virtual void BindValueToReference(const ValueNodeView& node, + const Address& a) = 0; + + // Returns whether the value bound at the given node is still alive. + virtual auto is_bound_value_alive(const ValueNodeView& node, + const Address& a) const -> bool = 0; + protected: HeapAllocationInterface() = default; virtual ~HeapAllocationInterface() = default; diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 7989e6e6f78c..9aa3a4f6d15d 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -16,6 +16,7 @@ #include "common/check.h" #include "common/error.h" +#include "explorer/ast/address.h" #include "explorer/ast/declaration.h" #include "explorer/ast/element.h" #include "explorer/ast/expression.h" @@ -86,6 +87,8 @@ class Interpreter { private: auto Step() -> ErrorOr; + // State transitions for expressions value generation. + auto StepValueExp() -> ErrorOr; // State transitions for expressions. auto StepExp() -> ErrorOr; // State transitions for lvalues. @@ -260,8 +263,13 @@ auto Interpreter::EvalPrim(Operator op, Nonnull /*static_type*/, cast(*args[1]).value()); case Operator::Ptr: return arena_->New(args[0]); - case Operator::Deref: - return heap_.Read(cast(*args[0]).address(), source_loc); + case Operator::Deref: { + CARBON_ASSIGN_OR_RETURN( + const auto* value, + heap_.Read(cast(*args[0]).address(), source_loc)); + return arena_->New( + value, cast(*args[0]).address()); + } case Operator::AddressOf: return arena_->New(cast(*args[0]).address()); case Operator::As: @@ -294,38 +302,39 @@ auto Interpreter::CreateStruct(const std::vector& fields, return arena_->New(std::move(elements)); } -static auto InitializePlaceholderValue( - const ValueNodeView& value_node, ExpressionResult v, - std::optional> bindings) { +static auto InitializePlaceholderValue(const ValueNodeView& value_node, + ExpressionResult v, + Nonnull bindings) { switch (value_node.expression_category()) { case ExpressionCategory::Reference: if (v.expression_category() == ExpressionCategory::Value || v.expression_category() == ExpressionCategory::Reference) { // Build by copying from value or reference expression. - (*bindings)->Initialize(value_node, v.value()); + bindings->Initialize(value_node, v.value()); } else { // Location initialized by initializing expression, bind node to // address. CARBON_CHECK(v.address()) << "Missing location from initializing expression"; - (*bindings)->Bind(value_node, *v.address()); + bindings->Bind(value_node, *v.address()); } break; case ExpressionCategory::Value: if (v.expression_category() == ExpressionCategory::Value) { // TODO: Ensure value expressions of temporaries are registered as // allocation to allow us to reference it without the need for a copy. - (*bindings)->Initialize(value_node, v.value()); + bindings->Initialize(value_node, v.value()); } else if (v.expression_category() == ExpressionCategory::Reference) { - // TODO: Prevent mutation, error on mutation, or copy // Bind the reference expression value directly. - (*bindings)->BindValue(value_node, v.value()); + CARBON_CHECK(v.address()) + << "Missing location from reference expression"; + bindings->BindAndPin(value_node, *v.address()); } else { // Location initialized by initializing expression, bind node to // address. CARBON_CHECK(v.address()) << "Missing location from initializing expression"; - (*bindings)->Bind(value_node, *v.address()); + bindings->Bind(value_node, *v.address()); } break; case ExpressionCategory::Initializing: @@ -344,12 +353,23 @@ auto PatternMatch(Nonnull p, ExpressionResult v, << ExpressionCategoryToString(v.expression_category()) << " expression with value " << *v.value() << "\n"; } + const auto make_expr_result = + [](Nonnull v) -> ExpressionResult { + if (const auto* expr_v = dyn_cast(v)) { + return ExpressionResult::Reference(expr_v->value(), expr_v->address()); + } + return ExpressionResult::Value(v); + }; + if (v.value()->kind() == Value::Kind::ReferenceExpressionValue) { + return PatternMatch(p, make_expr_result(v.value()), source_loc, bindings, + generic_args, trace_stream, arena); + } switch (p->kind()) { case Value::Kind::BindingPlaceholderValue: { CARBON_CHECK(bindings.has_value()); const auto& placeholder = cast(*p); if (placeholder.value_node().has_value()) { - InitializePlaceholderValue(*placeholder.value_node(), v, bindings); + InitializePlaceholderValue(*placeholder.value_node(), v, *bindings); } return true; } @@ -377,9 +397,8 @@ auto PatternMatch(Nonnull p, ExpressionResult v, CARBON_CHECK(p_tup.elements().size() == v_tup.elements().size()); for (size_t i = 0; i < p_tup.elements().size(); ++i) { if (!PatternMatch(p_tup.elements()[i], - ExpressionResult::Value(v_tup.elements()[i]), - source_loc, bindings, generic_args, trace_stream, - arena)) { + make_expr_result(v_tup.elements()[i]), source_loc, + bindings, generic_args, trace_stream, arena)) { return false; } } // for @@ -575,7 +594,7 @@ auto Interpreter::StepLocation() -> ErrorOr { &cast(exp).object())); } else if (act.pos() == 1) { - return todo_.Spawn(std::make_unique( + return todo_.Spawn(std::make_unique( &cast(exp).offset())); } else { // { v :: [][i] :: C, E, F} :: S, H} @@ -598,7 +617,7 @@ auto Interpreter::StepLocation() -> ErrorOr { } if (act.pos() == 0) { return todo_.Spawn( - std::make_unique(op.arguments()[0])); + std::make_unique(op.arguments()[0])); } else { const auto& res = cast(*act.results()[0]); return todo_.FinishAction(arena_->New(res.address())); @@ -1030,6 +1049,17 @@ auto Interpreter::Convert(Nonnull value, // parameterized types for pointers in function call parameters. return value; } + case Value::Kind::ReferenceExpressionValue: { + const auto* expr_value = cast(value); + CARBON_ASSIGN_OR_RETURN( + Nonnull converted, + Convert(expr_value->value(), destination_type, source_loc)); + if (converted == expr_value->value()) { + return expr_value; + } else { + return converted; + } + } } } @@ -1141,25 +1171,25 @@ auto Interpreter::CallFunction(const CallExpression& call, } else { // Mutable self with `[addr self: Self*]` CARBON_CHECK(isa(self_pattern)); - CARBON_CHECK(PatternMatch( + bool success = PatternMatch( self_pattern, ExpressionResult::Value(method_val->receiver()), call.source_loc(), &function_scope, generic_args, trace_stream_, - this->arena_)); + this->arena_); + CARBON_CHECK(success) << "Failed to bind addr self"; } } - // TODO: Preserve expression category to allow appropriate binding in - // `PatternMatch`. CARBON_ASSIGN_OR_RETURN( Nonnull converted_args, Convert(arg, &function.param_pattern().static_type(), call.source_loc())); // Bind the arguments to the parameters. - CARBON_CHECK(PatternMatch(&function.param_pattern().value(), - ExpressionResult::Value(converted_args), - call.source_loc(), &function_scope, - generic_args, trace_stream_, this->arena_)); + bool success = PatternMatch(&function.param_pattern().value(), + ExpressionResult::Value(converted_args), + call.source_loc(), &function_scope, + generic_args, trace_stream_, this->arena_); + CARBON_CHECK(success) << "Failed to bind arguments to parameters"; return todo_.Spawn(std::make_unique(*function.body(), location_received), std::move(function_scope)); @@ -1299,6 +1329,25 @@ auto Interpreter::StepInstantiateType() -> ErrorOr { } } +auto Interpreter::StepValueExp() -> ErrorOr { + auto& act = cast(todo_.CurrentAction()); + if (act.pos() == 0) { + return todo_.Spawn(std::make_unique( + &act.expression(), /*preserve_nested_categories=*/false, + act.location_received())); + } else { + CARBON_CHECK(act.results().size() == 1); + if (const auto* expr_value = + dyn_cast(act.results()[0])) { + // Unwrap the ExpressionAction to only keep the resulting + // `Value*`. + return todo_.FinishAction(expr_value->value()); + } else { + return todo_.FinishAction(act.results()[0]); + } + } +} + auto Interpreter::StepExp() -> ErrorOr { auto& act = cast(todo_.CurrentAction()); const Expression& exp = act.expression(); @@ -1311,10 +1360,10 @@ auto Interpreter::StepExp() -> ErrorOr { if (act.pos() == 0) { // { { e[i] :: C, E, F} :: S, H} // -> { { e :: [][i] :: C, E, F} :: S, H} - return todo_.Spawn(std::make_unique( + return todo_.Spawn(std::make_unique( &cast(exp).object())); } else if (act.pos() == 1) { - return todo_.Spawn(std::make_unique( + return todo_.Spawn(std::make_unique( &cast(exp).offset())); } else { // { { v :: [][i] :: C, E, F} :: S, H} @@ -1340,8 +1389,12 @@ auto Interpreter::StepExp() -> ErrorOr { // H} // -> { { ek+1 :: (f1=v1,..., fk=vk, fk+1=[],...) :: C, E, F} :: S, // H} - return todo_.Spawn(std::make_unique( - cast(exp).fields()[act.pos()])); + const auto* field = cast(exp).fields()[act.pos()]; + if (act.preserve_nested_categories()) { + return todo_.Spawn(std::make_unique(field, false)); + } else { + return todo_.Spawn(std::make_unique(field)); + } } else { return todo_.FinishAction(arena_->New(act.results())); } @@ -1349,7 +1402,7 @@ auto Interpreter::StepExp() -> ErrorOr { case ExpressionKind::StructLiteral: { const auto& literal = cast(exp); if (act.pos() < static_cast(literal.fields().size())) { - return todo_.Spawn(std::make_unique( + return todo_.Spawn(std::make_unique( &literal.fields()[act.pos()].expression())); } else { return todo_.FinishAction( @@ -1359,7 +1412,9 @@ auto Interpreter::StepExp() -> ErrorOr { case ExpressionKind::SimpleMemberAccessExpression: { const auto& access = cast(exp); if (auto rewrite = access.rewritten_form()) { - return todo_.ReplaceWith(std::make_unique(*rewrite)); + return todo_.ReplaceWith(std::make_unique( + *rewrite, act.preserve_nested_categories(), + act.location_received())); } if (act.pos() == 0) { // First, evaluate the first operand. @@ -1367,8 +1422,8 @@ auto Interpreter::StepExp() -> ErrorOr { return todo_.Spawn( std::make_unique(&access.object())); } else { - return todo_.Spawn( - std::make_unique(&access.object())); + return todo_.Spawn(std::make_unique( + &access.object(), /*preserve_nested_categories=*/false)); } } else { if (auto constant_value = access.constant_value()) { @@ -1393,9 +1448,14 @@ auto Interpreter::StepExp() -> ErrorOr { found_in_interface = cast(act.results().back()); } std::optional type_result; + const auto* result = + act.results()[0]->kind() == + Value::Kind::ReferenceExpressionValue + ? cast(act.results()[0])->value() + : act.results()[0]; if (!isa( - act.results()[0])) { - type_result = act.results()[0]; + result)) { + type_result = result; } MemberName* member_name = arena_->New( type_result, found_in_interface, member_name_type->member()); @@ -1441,21 +1501,37 @@ auto Interpreter::StepExp() -> ErrorOr { ElementPath::Component member(&access.member(), found_in_interface, witness); const Value* aggregate; + const Value* me_value; + std::optional
lhs_address; if (access.is_type_access()) { aggregate = act.results().back(); } else if (const auto* location = dyn_cast(act.results()[0])) { + lhs_address = location->address(); + me_value = act.results()[0]; CARBON_ASSIGN_OR_RETURN( aggregate, this->heap_.Read(location->address(), exp.source_loc())); + } else if (const auto* expr_value = + dyn_cast( + act.results()[0])) { + lhs_address = expr_value->address(); + aggregate = expr_value->value(); + me_value = aggregate; } else { aggregate = act.results()[0]; + me_value = aggregate; } CARBON_ASSIGN_OR_RETURN( Nonnull member_value, aggregate->GetElement(arena_, ElementPath(member), - exp.source_loc(), act.results()[0])); - return todo_.FinishAction(member_value); + exp.source_loc(), me_value)); + if (lhs_address) { + return todo_.FinishAction(arena_->New( + member_value, lhs_address->ElementAddress(member.element()))); + } else { + return todo_.FinishAction(member_value); + } } } } @@ -1470,7 +1546,7 @@ auto Interpreter::StepExp() -> ErrorOr { std::make_unique(&access.object())); } else { return todo_.Spawn( - std::make_unique(&access.object())); + std::make_unique(&access.object())); } } else { if (auto constant_value = access.constant_value()) { @@ -1563,7 +1639,7 @@ auto Interpreter::StepExp() -> ErrorOr { const auto& access = cast(exp); if (act.pos() == 0) { return todo_.Spawn( - std::make_unique(&access.object())); + std::make_unique(&access.object())); } else { ElementPath::Component base_elt(&access.element(), std::nullopt, std::nullopt); @@ -1584,6 +1660,10 @@ auto Interpreter::StepExp() -> ErrorOr { if (const auto* location = dyn_cast(value)) { CARBON_ASSIGN_OR_RETURN( value, heap_.Read(location->address(), exp.source_loc())); + if (ident.expression_category() == ExpressionCategory::Reference) { + return todo_.FinishAction(arena_->New( + value, location->address())); + } } return todo_.FinishAction(value); } @@ -1605,7 +1685,9 @@ auto Interpreter::StepExp() -> ErrorOr { case ExpressionKind::OperatorExpression: { const auto& op = cast(exp); if (auto rewrite = op.rewritten_form()) { - return todo_.ReplaceWith(std::make_unique(*rewrite)); + return todo_.ReplaceWith(std::make_unique( + *rewrite, act.preserve_nested_categories(), + act.location_received())); } if (act.pos() != static_cast(op.arguments().size())) { // { {v :: op(vs,[],e,es) :: C, E, F} :: S, H} @@ -1624,7 +1706,7 @@ auto Interpreter::StepExp() -> ErrorOr { } // No short-circuit, fall through to evaluate 2nd operand. } - return todo_.Spawn(std::make_unique(arg)); + return todo_.Spawn(std::make_unique(arg)); } else { // { {v :: op(vs,[]) :: C, E, F} :: S, H} // -> { {eval_prim(op, (vs,v)) :: C, E, F} :: S, H} @@ -1641,12 +1723,15 @@ auto Interpreter::StepExp() -> ErrorOr { // { {e1(e2) :: C, E, F} :: S, H} // -> { {e1 :: [](e2) :: C, E, F} :: S, H} return todo_.Spawn( - std::make_unique(&call.function())); + std::make_unique(&call.function())); } else if (act.pos() == 1) { // { { v :: [](e) :: C, E, F} :: S, H} // -> { { e :: v([]) :: C, E, F} :: S, H} - return todo_.Spawn( - std::make_unique(&call.argument())); + bool preserve_nested_categories = + (act.results()[0]->kind() != + Value::Kind::AlternativeConstructorValue); + return todo_.Spawn(std::make_unique( + &call.argument(), preserve_nested_categories)); } else if (num_witnesses > 0 && act.pos() < 2 + static_cast(num_witnesses)) { auto iter = call.witnesses().begin(); @@ -1675,17 +1760,19 @@ auto Interpreter::StepExp() -> ErrorOr { act.results()[2 + static_cast(num_witnesses)]); } } else { - CARBON_FATAL() << "in StepExp with Call pos " << act.pos(); + CARBON_FATAL() << "in StepValueExp with Call pos " << act.pos(); } } case ExpressionKind::IntrinsicExpression: { const auto& intrinsic = cast(exp); if (auto rewrite = intrinsic.rewritten_form()) { - return todo_.ReplaceWith(std::make_unique(*rewrite)); + return todo_.ReplaceWith(std::make_unique( + *rewrite, act.preserve_nested_categories(), + act.location_received())); } if (act.pos() == 0) { return todo_.Spawn( - std::make_unique(&intrinsic.args())); + std::make_unique(&intrinsic.args())); } // { {n :: C, E, F} :: S, H} -> { {n' :: C, E, F} :: S, H} const auto& args = cast(*act.results()[0]).elements(); @@ -1767,7 +1854,7 @@ auto Interpreter::StepExp() -> ErrorOr { return todo_.Spawn(std::make_unique( arena_->New(obj_addr), child_class_value)); } else { - heap_.Deallocate(obj_addr); + CARBON_RETURN_IF_ERROR(heap_.Deallocate(obj_addr)); return todo_.FinishAction(TupleValue::Empty()); } } else { @@ -1775,7 +1862,7 @@ auto Interpreter::StepExp() -> ErrorOr { return todo_.Spawn(std::make_unique( arena_->New(ptr->address()), pointee)); } else { - heap_.Deallocate(ptr->address()); + CARBON_RETURN_IF_ERROR(heap_.Deallocate(ptr->address())); return todo_.FinishAction(TupleValue::Empty()); } } @@ -1971,10 +2058,10 @@ auto Interpreter::StepExp() -> ErrorOr { const auto& if_expr = cast(exp); if (act.pos() == 0) { return todo_.Spawn( - std::make_unique(&if_expr.condition())); + std::make_unique(&if_expr.condition())); } else if (act.pos() == 1) { const auto& condition = cast(*act.results()[0]); - return todo_.Spawn(std::make_unique( + return todo_.Spawn(std::make_unique( condition.value() ? &if_expr.then_expression() : &if_expr.else_expression())); } else { @@ -1985,15 +2072,18 @@ auto Interpreter::StepExp() -> ErrorOr { case ExpressionKind::WhereExpression: { auto rewrite = cast(exp).rewritten_form(); CARBON_CHECK(rewrite) << "where expression should be rewritten"; - return todo_.ReplaceWith(std::make_unique(*rewrite)); + return todo_.ReplaceWith(std::make_unique( + *rewrite, act.preserve_nested_categories(), act.location_received())); } case ExpressionKind::BuiltinConvertExpression: { const auto& convert_expr = cast(exp); if (auto rewrite = convert_expr.rewritten_form()) { - return todo_.ReplaceWith(std::make_unique(*rewrite)); + return todo_.ReplaceWith(std::make_unique( + *rewrite, act.preserve_nested_categories(), + act.location_received())); } if (act.pos() == 0) { - return todo_.Spawn(std::make_unique( + return todo_.Spawn(std::make_unique( convert_expr.source_expression())); } else if (act.pos() == 1) { return todo_.Spawn(std::make_unique( @@ -2096,7 +2186,7 @@ auto Interpreter::StepStmt() -> ErrorOr { // -> { { e :: (match ([]) ...) :: C, E, F} :: S, H} act.StartScope(RuntimeScope(&heap_)); return todo_.Spawn( - std::make_unique(&match_stmt.expression())); + std::make_unique(&match_stmt.expression())); } else { int clause_num = act.pos() - 1; if (clause_num >= static_cast(match_stmt.clauses().size())) { @@ -2129,8 +2219,8 @@ auto Interpreter::StepStmt() -> ErrorOr { const auto* loop_var = &cast( cast(stmt).variable_declaration().value()); if (act.pos() == 0) { - return todo_.Spawn( - std::make_unique(&cast(stmt).loop_target())); + return todo_.Spawn(std::make_unique( + &cast(stmt).loop_target())); } if (act.pos() == 1) { const auto* source_array = @@ -2185,8 +2275,8 @@ auto Interpreter::StepStmt() -> ErrorOr { // { { (while (e) s) :: C, E, F} :: S, H} // -> { { e :: (while ([]) s) :: C, E, F} :: S, H} act.Clear(); - return todo_.Spawn( - std::make_unique(&cast(stmt).condition())); + return todo_.Spawn(std::make_unique( + &cast(stmt).condition())); } else { CARBON_ASSIGN_OR_RETURN( Nonnull condition, @@ -2256,7 +2346,8 @@ auto Interpreter::StepStmt() -> ErrorOr { todo_.MergeScope(std::move(scope)); } return todo_.Spawn(std::make_unique( - &definition.init(), init_location)); + &definition.init(), /*preserve_nested_categories=*/false, + init_location)); } else { // { { v :: (x = []) :: C, E, F} :: S, H} // -> { { C, E(x := a), F} :: S, H(a := copy(v))} @@ -2267,13 +2358,26 @@ auto Interpreter::StepStmt() -> ErrorOr { definition.has_init() ? definition.init().expression_category() : ExpressionCategory::Value; if (definition.has_init()) { - if (has_initializing_expr && init_location && - heap_.is_initialized(*init_location)) { - const auto address = Address(*init_location); + Nonnull result = act.results()[0]; + std::optional> v_expr = + (result->kind() == Value::Kind::ReferenceExpressionValue) + ? std::optional{cast(result)} + : std::nullopt; + const auto init_location = act.location_created(); + v = v_expr ? (*v_expr)->value() : result; + if (expr_category == ExpressionCategory::Reference) { + CARBON_CHECK(v_expr) << "Expecting ReferenceExpressionValue from " + "reference expression"; + v_location = (*v_expr)->address(); + CARBON_CHECK(v_location) + << "Expecting a valid address from reference expression"; + } else if (has_initializing_expr && init_location && + heap_.is_initialized(*init_location)) { + // Bind even if a conversion is necessary. + v_location = Address(*init_location); CARBON_ASSIGN_OR_RETURN( - v, heap_.Read(address, definition.source_loc())); - CARBON_CHECK(v == act.results()[0]); - v_location = address; + result, heap_.Read(*v_location, definition.source_loc())); + CARBON_CHECK(v == result); } else { // TODO: Prevent copies for Value expressions from Reference // expression, once able to prevent mutations. @@ -2283,8 +2387,8 @@ auto Interpreter::StepStmt() -> ErrorOr { } expr_category = ExpressionCategory::Value; const auto* dest_type = &definition.pattern().static_type(); - CARBON_ASSIGN_OR_RETURN( - v, Convert(act.results()[0], dest_type, stmt.source_loc())); + CARBON_ASSIGN_OR_RETURN(v, + Convert(v, dest_type, stmt.source_loc())); } } else { v = arena_->New(p); @@ -2319,7 +2423,7 @@ auto Interpreter::StepStmt() -> ErrorOr { if (act.pos() == 0) { // { {e :: C, E, F} :: S, H} // -> { {e :: C, E, F} :: S, H} - return todo_.Spawn(std::make_unique( + return todo_.Spawn(std::make_unique( &cast(stmt).expression())); } else { return todo_.FinishAction(); @@ -2328,7 +2432,7 @@ auto Interpreter::StepStmt() -> ErrorOr { const auto& assign = cast(stmt); if (auto rewrite = assign.rewritten_form()) { if (act.pos() == 0) { - return todo_.Spawn(std::make_unique(*rewrite)); + return todo_.Spawn(std::make_unique(*rewrite)); } else { return todo_.FinishAction(); } @@ -2340,7 +2444,8 @@ auto Interpreter::StepStmt() -> ErrorOr { } else if (act.pos() == 1) { // { { a :: ([] = e) :: C, E, F} :: S, H} // -> { { e :: (a = []) :: C, E, F} :: S, H} - return todo_.Spawn(std::make_unique(&assign.rhs())); + return todo_.Spawn( + std::make_unique(&assign.rhs())); } else { // { { v :: (a = []) :: C, E, F} :: S, H} // -> { { C, E, F} :: S, H(a := v)} @@ -2358,7 +2463,7 @@ auto Interpreter::StepStmt() -> ErrorOr { const auto& inc_dec = cast(stmt); if (act.pos() == 0) { return todo_.Spawn( - std::make_unique(*inc_dec.rewritten_form())); + std::make_unique(*inc_dec.rewritten_form())); } else { return todo_.FinishAction(); } @@ -2367,8 +2472,8 @@ auto Interpreter::StepStmt() -> ErrorOr { if (act.pos() == 0) { // { {(if (e) then_stmt else else_stmt) :: C, E, F} :: S, H} // -> { { e :: (if ([]) then_stmt else else_stmt) :: C, E, F} :: S, H} - return todo_.Spawn( - std::make_unique(&cast(stmt).condition())); + return todo_.Spawn(std::make_unique( + &cast(stmt).condition())); } else if (act.pos() == 1) { CARBON_ASSIGN_OR_RETURN( Nonnull condition, @@ -2418,7 +2523,7 @@ auto Interpreter::StepStmt() -> ErrorOr { if (act.pos() == 0) { // { {return e :: C, E, F} :: S, H} // -> { {e :: return [] :: C, E, F} :: S, H} - return todo_.Spawn(std::make_unique( + return todo_.Spawn(std::make_unique( &cast(stmt).expression())); } else { // { {v :: return [] :: C, E, F} :: {C', E', F'} :: S, H} @@ -2453,7 +2558,7 @@ auto Interpreter::StepDeclaration() -> ErrorOr { if (var_decl.has_initializer()) { if (act.pos() == 0) { return todo_.Spawn( - std::make_unique(&var_decl.initializer())); + std::make_unique(&var_decl.initializer())); } else { CARBON_ASSIGN_OR_RETURN( Nonnull v, @@ -2583,8 +2688,7 @@ auto Interpreter::StepCleanUp() -> ErrorOr { } if (act.pos() % 2 == 0) { auto* location = arena_->New(Address(allocation)); - auto value = - heap_.Read(location->address(), SourceLocation("destructor", 1)); + auto value = heap_.Read(location->address(), *cleanup.source_loc()); // Step over uninitialized values. if (value.ok()) { return todo_.Spawn(std::make_unique(location, *value)); @@ -2592,7 +2696,7 @@ auto Interpreter::StepCleanUp() -> ErrorOr { return todo_.RunAgain(); } } else { - heap_.Deallocate(allocation); + CARBON_RETURN_IF_ERROR(heap_.Deallocate(allocation)); return todo_.RunAgain(); } } @@ -2621,6 +2725,9 @@ auto Interpreter::Step() -> ErrorOr { case Action::Kind::LocationAction: CARBON_RETURN_IF_ERROR(StepLocation()); break; + case Action::Kind::ValueExpressionAction: + CARBON_RETURN_IF_ERROR(StepValueExp()); + break; case Action::Kind::ExpressionAction: CARBON_RETURN_IF_ERROR(StepExp()); break; @@ -2688,7 +2795,7 @@ auto InterpProgram(const AST& ast, Nonnull arena, CARBON_CHECK(ast.main_call); set_file_ctx.update_source_loc(ast.main_call.value()->source_loc()); CARBON_RETURN_IF_ERROR(interpreter.RunAllSteps( - std::make_unique(*ast.main_call))); + std::make_unique(*ast.main_call))); return cast(*interpreter.result()).value(); } @@ -2700,7 +2807,7 @@ auto InterpExp(Nonnull e, Nonnull arena, Interpreter interpreter(Phase::CompileTime, arena, trace_stream, print_stream); CARBON_RETURN_IF_ERROR( - interpreter.RunAllSteps(std::make_unique(e))); + interpreter.RunAllSteps(std::make_unique(e))); return interpreter.result(); } diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index 572f78eff5ee..a5433892247a 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -91,6 +91,7 @@ static auto IsTypeOfType(Nonnull value) -> bool { case Value::Kind::BoundMethodValue: case Value::Kind::PointerValue: case Value::Kind::LocationValue: + case Value::Kind::ReferenceExpressionValue: case Value::Kind::BoolValue: case Value::Kind::TupleValue: case Value::Kind::StructValue: @@ -151,6 +152,7 @@ static auto IsType(Nonnull value) -> bool { case Value::Kind::BoundMethodValue: case Value::Kind::PointerValue: case Value::Kind::LocationValue: + case Value::Kind::ReferenceExpressionValue: case Value::Kind::BoolValue: case Value::Kind::TupleValue: case Value::Kind::StructValue: @@ -220,6 +222,7 @@ static auto ExpectCompleteType(SourceLocation source_loc, case Value::Kind::BoundMethodValue: case Value::Kind::PointerValue: case Value::Kind::LocationValue: + case Value::Kind::ReferenceExpressionValue: case Value::Kind::BoolValue: case Value::Kind::StructValue: case Value::Kind::TupleValue: @@ -332,6 +335,7 @@ static auto TypeIsDeduceable(Nonnull type) -> bool { case Value::Kind::BoundMethodValue: case Value::Kind::PointerValue: case Value::Kind::LocationValue: + case Value::Kind::ReferenceExpressionValue: case Value::Kind::BoolValue: case Value::Kind::TupleValue: case Value::Kind::StructValue: @@ -1534,6 +1538,7 @@ auto TypeChecker::ArgumentDeduction::Deduce(Nonnull param, case Value::Kind::BoundMethodValue: case Value::Kind::PointerValue: case Value::Kind::LocationValue: + case Value::Kind::ReferenceExpressionValue: case Value::Kind::StructValue: case Value::Kind::TupleValue: case Value::Kind::NominalClassValue: @@ -6254,6 +6259,7 @@ static auto IsValidTypeForAliasTarget(Nonnull type) -> bool { case Value::Kind::BoundMethodValue: case Value::Kind::PointerValue: case Value::Kind::LocationValue: + case Value::Kind::ReferenceExpressionValue: case Value::Kind::BoolValue: case Value::Kind::StructValue: case Value::Kind::NominalClassValue: diff --git a/explorer/interpreter/type_structure.cpp b/explorer/interpreter/type_structure.cpp index d7848d9113ae..1f200c304301 100644 --- a/explorer/interpreter/type_structure.cpp +++ b/explorer/interpreter/type_structure.cpp @@ -7,6 +7,7 @@ #include #include "explorer/ast/declaration.h" +#include "explorer/ast/expression_category.h" #include "explorer/ast/value.h" #include "llvm/ADT/StringExtras.h" @@ -76,6 +77,7 @@ struct TypeStructureBuilder { // Ignore values that can't contain holes. void Visit(int) {} void Visit(std::string_view) {} + void Visit(ExpressionCategory) {} void Visit(Nonnull) {} void Visit(const ValueNodeView&) {} void Visit(const Address&) {} diff --git a/explorer/testdata/let/fail_pinned_value_changed.carbon b/explorer/testdata/let/fail_pinned_value_changed.carbon new file mode 100644 index 000000000000..10b306a48c02 --- /dev/null +++ b/explorer/testdata/let/fail_pinned_value_changed.carbon @@ -0,0 +1,16 @@ +// 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 + +package ExplorerTest api; + +fn Main() -> i32 { + var a: i32 = 1; + let a_pinned: i32 = a; + a = 2; + // CHECK:STDERR: RUNTIME ERROR: fail_pinned_value_changed.carbon:[[@LINE+1]]: Reference has changed since this value was bound. + Print("{0}", a_pinned); + return 0; +} diff --git a/explorer/testdata/let/fail_pinned_value_restored.carbon b/explorer/testdata/let/fail_pinned_value_restored.carbon new file mode 100644 index 000000000000..7fe9854a3c5f --- /dev/null +++ b/explorer/testdata/let/fail_pinned_value_restored.carbon @@ -0,0 +1,18 @@ +// 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 + +package ExplorerTest api; + +fn Main() -> i32 { + let original: i32 = 1; + var a: i32 = original; + let a_pinned: i32 = a; + a = 2; + a = original; + // CHECK:STDERR: RUNTIME ERROR: fail_pinned_value_restored.carbon:[[@LINE+1]]: Reference has changed since this value was bound. + Print("{0}", a_pinned); + return 0; +} diff --git a/explorer/testdata/let/fail_pinned_value_subobject_changed.carbon b/explorer/testdata/let/fail_pinned_value_subobject_changed.carbon new file mode 100644 index 000000000000..107d01e9d73f --- /dev/null +++ b/explorer/testdata/let/fail_pinned_value_subobject_changed.carbon @@ -0,0 +1,24 @@ +// 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 + +package ExplorerTest api; + +class C { + var i: i32; +} + +fn Foo(c: C) { + Print("{0}", c.i); +} + +fn Main() -> i32 { + var c: C = { .i = 1 }; + let pinned_c: C = c; + c.i = 2; + // CHECK:STDERR: RUNTIME ERROR: fail_pinned_value_subobject_changed.carbon:[[@LINE+1]]: Reference has changed since this value was bound. + Foo(pinned_c); + return 0; +} diff --git a/explorer/testdata/let/pinned_value_copied.carbon b/explorer/testdata/let/pinned_value_copied.carbon new file mode 100644 index 000000000000..86324c081c81 --- /dev/null +++ b/explorer/testdata/let/pinned_value_copied.carbon @@ -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 +// CHECK:STDOUT: a: 2 +// CHECK:STDOUT: a_pinned2: 1 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +fn Main() -> i32 { + var a: i32 = 1; + let a_pinned: i32 = a; + let a_pinned_copy: i32 = a_pinned; + + // OK: Value unused after being mutated. + a = 2; + + Print("a: {0}", a); + + // OK: Value was copied from `a_pinned`, and reflects previous value. + // TODO: Avoid value->value copy when possible. + Print("a_pinned2: {0}", a_pinned_copy); + + return 0; +} diff --git a/explorer/testdata/let/pinned_value_multiple_times.carbon b/explorer/testdata/let/pinned_value_multiple_times.carbon new file mode 100644 index 000000000000..fa9ea83a7f25 --- /dev/null +++ b/explorer/testdata/let/pinned_value_multiple_times.carbon @@ -0,0 +1,17 @@ +// 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 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +fn Main() -> i32 { + var a: i32 = 1; + let a_pinned: i32 = a; + let a_pinned2: i32 = a; + // OK: Value unused after being mutated. + a = 2; + return 0; +} diff --git a/explorer/testdata/let/pinned_value_mutation_unread_data.carbon b/explorer/testdata/let/pinned_value_mutation_unread_data.carbon new file mode 100644 index 000000000000..514949218a9c --- /dev/null +++ b/explorer/testdata/let/pinned_value_mutation_unread_data.carbon @@ -0,0 +1,21 @@ +// 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 +// CHECK:STDOUT: 1 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +fn Foo(n: i32) { + Print("{0}", n); +} + +fn Main() -> i32 { + var v: {.a: i32, .b: i32} = {.a = 1, .b = 2}; + let a: i32 = v.a; + v.b = 3; + Foo(a); + return 0; +} diff --git a/explorer/testdata/let/pinned_value_mutation_unused.carbon b/explorer/testdata/let/pinned_value_mutation_unused.carbon new file mode 100644 index 000000000000..0c94487e789f --- /dev/null +++ b/explorer/testdata/let/pinned_value_mutation_unused.carbon @@ -0,0 +1,16 @@ +// 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 +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +fn Main() -> i32 { + var a: i32 = 1; + let a_pinned: i32 = a; + // OK: Value unused after being mutated. + a = 2; + return 0; +} diff --git a/explorer/testdata/let/value_expr_binding_from_reference.carbon b/explorer/testdata/let/value_expr_binding_from_reference.carbon index 58f4a92ef4b5..d004fa0ff87c 100644 --- a/explorer/testdata/let/value_expr_binding_from_reference.carbon +++ b/explorer/testdata/let/value_expr_binding_from_reference.carbon @@ -6,8 +6,7 @@ // CHECK:STDOUT: 0: Heap{}, 1: C{} // CHECK:STDOUT: Bind from c reference expression // CHECK:STDOUT: Binding scope end -// CHECK:STDOUT: c destroyed -// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: !!C{} +// CHECK:STDOUT: 0: Heap{}, 1: C{} // CHECK:STDOUT: c destroyed // CHECK:STDOUT: result: 0 diff --git a/explorer/testdata/let/value_expr_from_ref.carbon b/explorer/testdata/let/value_expr_from_reference.carbon similarity index 90% rename from explorer/testdata/let/value_expr_from_ref.carbon rename to explorer/testdata/let/value_expr_from_reference.carbon index 99b63b94f11a..46cb82ab990a 100644 --- a/explorer/testdata/let/value_expr_from_ref.carbon +++ b/explorer/testdata/let/value_expr_from_reference.carbon @@ -5,8 +5,7 @@ // AUTOUPDATE // CHECK:STDOUT: 0: Heap{}, 1: C{} // CHECK:STDOUT: Initialize c from reference expression -// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: C{} -// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: 0: Heap{}, 1: C{} // CHECK:STDOUT: c destroyed // CHECK:STDOUT: result: 0 diff --git a/explorer/testdata/let/value_expr_from_reference_deref.carbon b/explorer/testdata/let/value_expr_from_reference_deref.carbon new file mode 100644 index 000000000000..7a9c4dafa3c1 --- /dev/null +++ b/explorer/testdata/let/value_expr_from_reference_deref.carbon @@ -0,0 +1,32 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: ptr +// CHECK:STDOUT: Initialize c from reference expression from deferenced pointer +// CHECK:STDOUT: 0: Heap{}, 1: C{}, 2: ptr +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class C { + destructor[self: Self] { + Print("c destroyed"); + } +} + +fn FromReferenceExpressionDeref() { + var c_var: C = {}; + var c_ref: C* = &c_var; + heap.PrintAllocs(); + Print("Initialize c from reference expression from deferenced pointer"); + let c: C = *c_ref; + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromReferenceExpressionDeref(); + return 0; +} diff --git a/explorer/testdata/let/value_expr_from_reference_subobject.carbon b/explorer/testdata/let/value_expr_from_reference_subobject.carbon new file mode 100644 index 000000000000..d911b8780b4f --- /dev/null +++ b/explorer/testdata/let/value_expr_from_reference_subobject.carbon @@ -0,0 +1,39 @@ +// 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 +// CHECK:STDOUT: 0: Heap{}, 1: C{.d = D{}} +// CHECK:STDOUT: Initialize d from reference expression from subobject +// CHECK:STDOUT: 0: Heap{}, 1: C{.d = D{}} +// CHECK:STDOUT: c destroyed +// CHECK:STDOUT: d destroyed +// CHECK:STDOUT: result: 0 + +package ExplorerTest api; + +class D { + destructor[self: Self] { + Print("d destroyed"); + } +} + +class C { + destructor[self: Self] { + Print("c destroyed"); + } + var d: D; +} + +fn FromReferenceExpressionDeref() { + var c_var: C = {.d = {}}; + heap.PrintAllocs(); + Print("Initialize d from reference expression from subobject"); + let d: D = c_var.d; + heap.PrintAllocs(); +} + +fn Main() -> i32 { + FromReferenceExpressionDeref(); + return 0; +} diff --git a/explorer/testdata/var/local/reference_expr_from_ref.carbon b/explorer/testdata/var/local/reference_expr_from_reference.carbon similarity index 100% rename from explorer/testdata/var/local/reference_expr_from_ref.carbon rename to explorer/testdata/var/local/reference_expr_from_reference.carbon diff --git a/explorer/trace_testdata/full_trace.carbon b/explorer/trace_testdata/full_trace.carbon index d7a9f5b89795..4a92abf4c1d1 100644 --- a/explorer/trace_testdata/full_trace.carbon +++ b/explorer/trace_testdata/full_trace.carbon @@ -29,13 +29,21 @@ // CHECK:STDOUT: stack:{{ }} // CHECK:STDOUT: memory:{{ }} // CHECK:STDOUT: } +// CHECK:STDOUT: { +// CHECK:STDOUT: stack: i32 .0. ## i32 .1. +// CHECK:STDOUT: memory:{{ }} +// CHECK:STDOUT: } package ExplorerTest api; interface TestInterface {} -// CHECK:STDOUT: --- step exp i32 .0. (full_trace.carbon:[[@LINE+49]]) ---> +// CHECK:STDOUT: --- step exp i32 .0. (full_trace.carbon:[[@LINE+53]]) ---> +// CHECK:STDOUT: { +// CHECK:STDOUT: stack: i32 .1. {{[[][[]}}i32]] +// CHECK:STDOUT: memory:{{ }} +// CHECK:STDOUT: } // CHECK:STDOUT: { // CHECK:STDOUT: stack:{{ }} // CHECK:STDOUT: memory:{{ }} @@ -75,7 +83,7 @@ interface TestInterface {} // CHECK:STDOUT: stack:{{ }} // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } -// CHECK:STDOUT: --- step decl interface TestInterface .0. (full_trace.carbon:[[@LINE-42]]) ---> +// CHECK:STDOUT: --- step decl interface TestInterface .0. (full_trace.carbon:[[@LINE-46]]) ---> // CHECK:STDOUT: { // CHECK:STDOUT: stack:{{ }} // CHECK:STDOUT: memory: 0: Heap{} @@ -86,7 +94,7 @@ interface TestInterface {} // CHECK:STDOUT: } fn Main() -> i32 { return 0; -// CHECK:STDOUT: --- step decl fn Main .0. (full_trace.carbon:[[@LINE+82]]) ---> +// CHECK:STDOUT: --- step decl fn Main .0. (full_trace.carbon:[[@LINE+106]]) ---> // CHECK:STDOUT: { // CHECK:STDOUT: stack:{{ }} // CHECK:STDOUT: memory: 0: Heap{} @@ -96,24 +104,36 @@ fn Main() -> i32 { // CHECK:STDOUT: stack:{{ }} // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } +// CHECK:STDOUT: { +// CHECK:STDOUT: stack: Main() .0. ## Main() .1. +// CHECK:STDOUT: memory: 0: Heap{} +// CHECK:STDOUT: } // CHECK:STDOUT: --- step exp Main() .0. (:0) ---> // CHECK:STDOUT: { -// CHECK:STDOUT: stack: Main .0. ## Main() .1. +// CHECK:STDOUT: stack: Main .0. ## Main() .1. ## Main() .1. +// CHECK:STDOUT: memory: 0: Heap{} +// CHECK:STDOUT: } +// CHECK:STDOUT: { +// CHECK:STDOUT: stack: Main .0. ## Main .1. ## Main() .1. ## Main() .1. // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } // CHECK:STDOUT: --- step exp Main .0. (:0) ---> // CHECK:STDOUT: { -// CHECK:STDOUT: stack: Main() .1. {{[[][[]}}fun
]] +// CHECK:STDOUT: stack: Main .1. {{[[][[]}}fun
]] ## Main() .1. ## Main() .1. +// CHECK:STDOUT: memory: 0: Heap{} +// CHECK:STDOUT: } +// CHECK:STDOUT: { +// CHECK:STDOUT: stack: Main() .1. {{[[][[]}}fun
]] ## Main() .1. // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } // CHECK:STDOUT: --- step exp Main() .1. (:0) ---> // CHECK:STDOUT: { -// CHECK:STDOUT: stack: () .0. ## Main() .2. {{[[][[]}}fun
]] +// CHECK:STDOUT: stack: () .0. ## Main() .2. {{[[][[]}}fun
]] ## Main() .1. // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } // CHECK:STDOUT: --- step exp () .0. (:0) ---> // CHECK:STDOUT: { -// CHECK:STDOUT: stack: Main() .2. {{[[][[]}}fun
, ()]] +// CHECK:STDOUT: stack: Main() .2. {{[[][[]}}fun
, ()]] ## Main() .1. // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } // CHECK:STDOUT: --- step exp Main() .2. (:0) ---> @@ -121,40 +141,52 @@ fn Main() -> i32 { // CHECK:STDOUT: match pattern () // CHECK:STDOUT: from value expression with value () // CHECK:STDOUT: { -// CHECK:STDOUT: stack: {return 0;} .0. ## .0. {} ## Main() .3. {{[[][[]}}fun
, ()]] {} +// CHECK:STDOUT: stack: {return 0;} .0. ## .0. {} ## Main() .3. {{[[][[]}}fun
, ()]] {} ## Main() .1. // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } -// CHECK:STDOUT: --- step stmt {return 0;} .0. (full_trace.carbon:[[@LINE+44]]) ---> +// CHECK:STDOUT: --- step stmt {return 0;} .0. (full_trace.carbon:[[@LINE+56]]) ---> // CHECK:STDOUT: { -// CHECK:STDOUT: stack: return 0; .0. ## {return 0;} .1. {} ## .0. {} ## Main() .3. {{[[][[]}}fun
, ()]] {} +// CHECK:STDOUT: stack: return 0; .0. ## {return 0;} .1. {} ## .0. {} ## Main() .3. {{[[][[]}}fun
, ()]] {} ## Main() .1. // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } -// CHECK:STDOUT: --- step stmt return 0; .0. (full_trace.carbon:[[@LINE-44]]) ---> +// CHECK:STDOUT: --- step stmt return 0; .0. (full_trace.carbon:[[@LINE-56]]) ---> // CHECK:STDOUT: { -// CHECK:STDOUT: stack: 0 .0. ## return 0; .1. ## {return 0;} .1. {} ## .0. {} ## Main() .3. {{[[][[]}}fun
, ()]] {} -// CHECK:STDOUT: memory: 0: Heap{} -// CHECK:STDOUT: } -// CHECK:STDOUT: --- step exp 0 .0. (full_trace.carbon:[[@LINE-49]]) ---> -// CHECK:STDOUT: { -// CHECK:STDOUT: stack: return 0; .1. {{[[][[]}}0]] ## {return 0;} .1. {} ## .0. {} ## Main() .3. {{[[][[]}}fun
, ()]] {} -// CHECK:STDOUT: memory: 0: Heap{} -// CHECK:STDOUT: } -// CHECK:STDOUT: --- step stmt return 0; .1. (full_trace.carbon:[[@LINE-54]]) ---> -// CHECK:STDOUT: { -// CHECK:STDOUT: stack: clean up.0. {} ## clean up.0. {} ## Main() .3. {{[[][[]}}fun
, (), 0]] {} +// CHECK:STDOUT: stack: 0 .0. ## return 0; .1. ## {return 0;} .1. {} ## .0. {} ## Main() .3. {{[[][[]}}fun
, ()]] {} ## Main() .1. // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } // CHECK:STDOUT: { -// CHECK:STDOUT: stack: clean up.0. {} ## Main() .3. {{[[][[]}}fun
, (), 0]] {} +// CHECK:STDOUT: stack: 0 .0. ## 0 .1. ## return 0; .1. ## {return 0;} .1. {} ## .0. {} ## Main() .3. {{[[][[]}}fun
, ()]] {} ## Main() .1. +// CHECK:STDOUT: memory: 0: Heap{} +// CHECK:STDOUT: } +// CHECK:STDOUT: --- step exp 0 .0. (full_trace.carbon:[[@LINE-65]]) ---> +// CHECK:STDOUT: { +// CHECK:STDOUT: stack: 0 .1. {{[[][[]}}0]] ## return 0; .1. ## {return 0;} .1. {} ## .0. {} ## Main() .3. {{[[][[]}}fun
, ()]] {} ## Main() .1. // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } // CHECK:STDOUT: { -// CHECK:STDOUT: stack: Main() .3. {{[[][[]}}fun
, (), 0]] {} +// CHECK:STDOUT: stack: return 0; .1. {{[[][[]}}0]] ## {return 0;} .1. {} ## .0. {} ## Main() .3. {{[[][[]}}fun
, ()]] {} ## Main() .1. +// CHECK:STDOUT: memory: 0: Heap{} +// CHECK:STDOUT: } +// CHECK:STDOUT: --- step stmt return 0; .1. (full_trace.carbon:[[@LINE-74]]) ---> +// CHECK:STDOUT: { +// CHECK:STDOUT: stack: clean up.0. {} ## clean up.0. {} ## Main() .3. {{[[][[]}}fun
, (), 0]] {} ## Main() .1. +// CHECK:STDOUT: memory: 0: Heap{} +// CHECK:STDOUT: } +// CHECK:STDOUT: { +// CHECK:STDOUT: stack: clean up.0. {} ## Main() .3. {{[[][[]}}fun
, (), 0]] {} ## Main() .1. +// CHECK:STDOUT: memory: 0: Heap{} +// CHECK:STDOUT: } +// CHECK:STDOUT: { +// CHECK:STDOUT: stack: Main() .3. {{[[][[]}}fun
, (), 0]] {} ## Main() .1. // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } // CHECK:STDOUT: --- step exp Main() .3. (:0) ---> // CHECK:STDOUT: { -// CHECK:STDOUT: stack: clean up.0. {} +// CHECK:STDOUT: stack: clean up.0. {} ## Main() .1. {{[[][[]}}0]] +// CHECK:STDOUT: memory: 0: Heap{} +// CHECK:STDOUT: } +// CHECK:STDOUT: { +// CHECK:STDOUT: stack: Main() .1. {{[[][[]}}0]] // CHECK:STDOUT: memory: 0: Heap{} // CHECK:STDOUT: } // CHECK:STDOUT: {