// 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 #ifndef CARBON_EXPLORER_INTERPRETER_ACTION_STACK_H_ #define CARBON_EXPLORER_INTERPRETER_ACTION_STACK_H_ #include #include #include #include "common/ostream.h" #include "explorer/ast/statement.h" #include "explorer/ast/value.h" #include "explorer/base/trace_stream.h" #include "explorer/interpreter/action.h" namespace Carbon { // Selects between compile-time and run-time behavior. enum class Phase { CompileTime, RunTime }; // The stack of Actions currently being executed by the interpreter. class ActionStack : public Printable { public: // Constructs an empty compile-time ActionStack. explicit ActionStack(Nonnull trace_stream) : phase_(Phase::CompileTime), trace_stream_(trace_stream) {} // Constructs an empty run-time ActionStack that allocates global variables // on `heap`. explicit ActionStack(Nonnull trace_stream, Nonnull heap) : globals_(RuntimeScope(heap)), phase_(Phase::RunTime), trace_stream_(trace_stream) {} void Print(llvm::raw_ostream& out) const; // Starts execution with `action` at the top of the stack. Cannot be called // when IsEmpty() is false. void Start(std::unique_ptr action); // True if the stack is empty. auto empty() const -> bool { return todo_.empty(); } // The Action currently at the top of the stack. This will never be a // ScopeAction. auto CurrentAction() -> Action& { return *todo_.Top(); } // Allocates storage for `value_node`, and initializes it to `value`. void Initialize(ValueNodeView value_node, Nonnull value); // Returns the value bound to `value_node`. If `value_node` is a local // variable, this will be an LocationValue. auto ValueOfNode(ValueNodeView value_node, SourceLocation source_loc) const -> ErrorOr>; // Merges `scope` into the innermost scope currently on the stack. void MergeScope(RuntimeScope scope); // The result produced by the `action` argument of the most recent // Start call. Cannot be called if IsEmpty() is false, or if `action` // was an action that doesn't produce results. auto result() const -> Nonnull { return *result_; } // The following methods, called "transition methods", update the state of // the ActionStack and/or the current Action to reflect the effects of // executing a step of that Action. Execution of an Action step should always // invoke exactly one transition method, as the very last operation. This is a // matter of safety as well as convention: most transition methods modify the // state of the current action, and some of them destroy it. To help enforce // this requirement, we have a convention of making these methods return an // ErrorOr even when a method can't actually fail, and calling the // methods as part of return statements, e.g. `return todo_.FinishAction()`. // Finishes execution of the current Action. If `result` is specified, it // represents the result of that Action. auto FinishAction() -> ErrorOr; auto FinishAction(Nonnull result) -> ErrorOr; // Advances the current action one step, and push `child` onto the stack. // If `scope` is specified, `child` will be executed in that scope. auto Spawn(std::unique_ptr child) -> ErrorOr; auto Spawn(std::unique_ptr child, RuntimeScope scope) -> ErrorOr; // Replace the current action with another action that produces the same kind // of result and run it next. auto ReplaceWith(std::unique_ptr replacement) -> ErrorOr; // Start a new recursive action. auto BeginRecursiveAction() { todo_.Push(std::make_unique()); } // Advances the current action one step. auto RunAgain() -> ErrorOr; // Unwinds Actions from the stack until the StatementAction associated with // `ast_node` is at the top of the stack. auto UnwindTo(Nonnull ast_node) -> ErrorOr; // Unwinds Actions from the stack until the StatementAction associated with // `ast_node` has been removed from the stack. If `result` is specified, // it represents the result of that Action (StatementActions normally cannot // produce results, but the body of a function can). auto UnwindPast(Nonnull ast_node) -> ErrorOr; auto UnwindPast(Nonnull ast_node, Nonnull result) -> ErrorOr; auto Pop() -> std::unique_ptr { auto popped_action = todo_.Pop(); if (trace_stream_->is_enabled()) { trace_stream_->Pop() << "stack-pop: " << *popped_action << " (" << popped_action->source_loc() << ")\n"; } return popped_action; } void Push(std::unique_ptr action) { if (trace_stream_->is_enabled()) { trace_stream_->Push() << "stack-push: " << *action << " (" << action->source_loc() << ")\n"; } todo_.Push(std::move(action)); } auto size() const -> int { return todo_.size(); } private: // Pop any ScopeActions from the top of the stack, propagating results as // needed, to restore the invariant that todo_.Top() is not a ScopeAction. // Store the popped scope action into cleanup_stack, so that the destructor // can be called for the variables void PopScopes(std::stack>& cleanup_stack); // Set `result` as the result of the Action most recently removed from the // stack. void SetResult(Nonnull result); auto UnwindToWithCaptureScopesToDestroy(Nonnull ast_node) -> std::stack>; auto UnwindPastWithCaptureScopesToDestroy(Nonnull ast_node) -> std::stack>; // Create CleanUpActions for all actions void PushCleanUpActions(std::stack> actions); // Create and push a CleanUpAction on the stack void PushCleanUpAction(std::unique_ptr act); // TODO: consider defining a non-nullable unique_ptr-like type to use here. Stack> todo_; std::optional> result_; std::optional globals_; Phase phase_; Nonnull trace_stream_; }; } // namespace Carbon #endif // CARBON_EXPLORER_INTERPRETER_ACTION_STACK_H_