diff --git a/.codespell_ignore b/.codespell_ignore index d8760c9a6fc8..b6b63d2ddae3 100644 --- a/.codespell_ignore +++ b/.codespell_ignore @@ -9,3 +9,4 @@ crate inout pullrequest statics +compiletime diff --git a/executable_semantics/interpreter/action_stack.h b/executable_semantics/interpreter/action_stack.h index 739de309f11d..85852068849b 100644 --- a/executable_semantics/interpreter/action_stack.h +++ b/executable_semantics/interpreter/action_stack.h @@ -18,23 +18,20 @@ namespace Carbon { // The stack of Actions currently being executed by the interpreter. class ActionStack { public: - // Constructs an empty ActionStack + // Constructs an empty compile-time ActionStack. ActionStack() = default; + // Constructs an empty run-time ActionStack that allocates global variables + // on `heap`. + explicit ActionStack(Nonnull heap) + : globals_(RuntimeScope(heap)) {} + void Print(llvm::raw_ostream& out) const; LLVM_DUMP_METHOD void Dump() const { Print(llvm::errs()); } // TODO: consider unifying with Print. void PrintScopes(llvm::raw_ostream& out) const; - // Sets the heap that variables will be allocated on. Cannot be called at - // run time, or when IsEmpty() is false, and marks the start of run time. - void SetHeap(Nonnull heap) { - CHECK(todo_.IsEmpty()); - CHECK(!globals_.has_value()); - globals_ = RuntimeScope(heap); - } - // Starts execution with `action` at the top of the stack. Cannot be called // when IsEmpty() is false. void Start(std::unique_ptr action); diff --git a/executable_semantics/interpreter/exec_program.cpp b/executable_semantics/interpreter/exec_program.cpp index c72d484c1361..4a8962eafdcb 100644 --- a/executable_semantics/interpreter/exec_program.cpp +++ b/executable_semantics/interpreter/exec_program.cpp @@ -41,7 +41,7 @@ void ExecProgram(Nonnull arena, AST ast, bool trace) { } llvm::outs() << "********** starting execution **********\n"; } - int result = Interpreter(arena, trace).InterpProgram(ast); + int result = InterpProgram(ast, arena, trace); llvm::outs() << "result: " << result << "\n"; } diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index e119c383d601..594150e04ab6 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -27,6 +27,91 @@ using llvm::isa; namespace Carbon { +// Selects between compile-time and run-time behavior. +enum class Phase { CompileTime, RunTime }; + +// Constructs an ActionStack suitable for the specified phase. +static auto MakeTodo(Phase phase, Nonnull heap) -> ActionStack { + switch (phase) { + case Phase::CompileTime: + return ActionStack(); + case Phase::RunTime: + return ActionStack(heap); + } +} + +// An Interpreter represents an instance of the Carbon abstract machine. It +// manages the state of the abstract machine, and executes the steps of Actions +// passed to it. +class Interpreter { + public: + // Constructs an Interpreter which allocates values on `arena`, and prints + // traces if `trace` is true. `phase` indicates whether it executes at + // compile time or run time. + Interpreter(Phase phase, Nonnull arena, bool trace) + : arena_(arena), + heap_(arena), + todo_(MakeTodo(phase, &heap_)), + trace_(trace) {} + + ~Interpreter(); + + // Runs all the steps of `action`. + void RunAllSteps(std::unique_ptr action); + + // The result produced by the `action` argument of the most recent + // RunAllSteps call. Cannot be called if `action` was an action that doesn't + // produce results. + auto result() const -> Nonnull { return todo_.result(); } + + private: + void Step(); + + // State transitions for expressions. + void StepExp(); + // State transitions for lvalues. + void StepLvalue(); + // State transitions for patterns. + void StepPattern(); + // State transition for statements. + void StepStmt(); + // State transition for declarations. + void StepDeclaration(); + + auto CreateStruct(const std::vector& fields, + const std::vector>& values) + -> Nonnull; + + auto EvalPrim(Operator op, const std::vector>& args, + SourceLocation source_loc) -> Nonnull; + + // Returns the result of converting `value` to type `destination_type`. + auto Convert(Nonnull value, + Nonnull destination_type) const + -> Nonnull; + + void PrintState(llvm::raw_ostream& out); + + Nonnull arena_; + + Heap heap_; + ActionStack todo_; + + // The underlying states of continuation values. All StackFragments created + // during execution are tracked here, in order to safely deallocate the + // contents of any non-completed continuations at the end of execution. + std::vector> stack_fragments_; + + bool trace_; +}; + +Interpreter::~Interpreter() { + // Clean up any remaining suspended continuations. + for (Nonnull fragment : stack_fragments_) { + fragment->Clear(); + } +} + // // State Operations // @@ -85,10 +170,9 @@ auto Interpreter::CreateStruct(const std::vector& fields, return arena_->New(std::move(elements)); } -auto Interpreter::PatternMatch(Nonnull p, Nonnull v, - SourceLocation source_loc, - std::optional> bindings) - -> bool { +auto PatternMatch(Nonnull p, Nonnull v, + SourceLocation source_loc, + std::optional> bindings) -> bool { switch (p->kind()) { case Value::Kind::BindingPlaceholderValue: { if (!bindings.has_value()) { @@ -864,59 +948,50 @@ void Interpreter::Step() { } // switch } -void Interpreter::RunAllSteps(bool trace_steps) { +void Interpreter::RunAllSteps(std::unique_ptr action) { + if (trace_) { + PrintState(llvm::outs()); + } + todo_.Start(std::move(action)); while (!todo_.IsEmpty()) { Step(); - if (trace_steps) { + if (trace_) { PrintState(llvm::outs()); } } } -auto Interpreter::InterpProgram(const AST& ast) -> int { - todo_.SetHeap(&heap_); - - if (trace_) { +auto InterpProgram(const AST& ast, Nonnull arena, bool trace) -> int { + Interpreter interpreter(Phase::RunTime, arena, trace); + if (trace) { llvm::outs() << "********** initializing globals **********\n"; } for (Nonnull declaration : ast.declarations) { - todo_.Start(std::make_unique(declaration)); - RunAllSteps(trace_); + interpreter.RunAllSteps(std::make_unique(declaration)); } - if (trace_) { + if (trace) { llvm::outs() << "********** calling main function **********\n"; - PrintState(llvm::outs()); } - todo_.Start(std::make_unique(*ast.main_call)); - RunAllSteps(trace_); + interpreter.RunAllSteps(std::make_unique(*ast.main_call)); - // Clean up any remaining suspended continuations. - for (Nonnull fragment : stack_fragments_) { - fragment->Clear(); - } - - return cast(*todo_.result()).value(); + return cast(*interpreter.result()).value(); } -auto Interpreter::RunCompileTimeAction(std::unique_ptr action) +auto InterpExp(Nonnull e, Nonnull arena, bool trace) -> Nonnull { - todo_.Start(std::move(action)); - RunAllSteps(/*trace_steps=*/false); - CHECK(stack_fragments_.empty()); - return todo_.result(); + Interpreter interpreter(Phase::CompileTime, arena, trace); + interpreter.RunAllSteps(std::make_unique(e)); + return interpreter.result(); } -auto Interpreter::InterpExp(Nonnull e) +auto InterpPattern(Nonnull p, Nonnull arena, bool trace) -> Nonnull { - return RunCompileTimeAction(std::make_unique(e)); -} - -auto Interpreter::InterpPattern(Nonnull p) - -> Nonnull { - return RunCompileTimeAction(std::make_unique(p)); + Interpreter interpreter(Phase::CompileTime, arena, trace); + interpreter.RunAllSteps(std::make_unique(p)); + return interpreter.result(); } } // namespace Carbon diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index 470338b8545c..4d83c93cdcca 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -22,87 +22,32 @@ namespace Carbon { -class Interpreter { - public: - explicit Interpreter(Nonnull arena, bool trace) - : arena_(arena), heap_(arena), trace_(trace) {} +// Interprets the program defined by `ast`, allocating values on `arena` and +// printing traces if `trace` is true. +auto InterpProgram(const AST& ast, Nonnull arena, bool trace) -> int; - // Interpret the whole program. - auto InterpProgram(const AST& ast) -> int; +// Interprets `e` at compile-time, allocating values on `arena` and +// printing traces if `trace` is true. +auto InterpExp(Nonnull e, Nonnull arena, bool trace) + -> Nonnull; - // Interpret an expression at compile-time. - auto InterpExp(Nonnull e) -> Nonnull; +// Interprets `p` at compile-time, allocating values on `arena` and +// printing traces if `trace` is true. +auto InterpPattern(Nonnull p, Nonnull arena, bool trace) + -> Nonnull; - // Interpret a pattern at compile-time. - auto InterpPattern(Nonnull p) -> Nonnull; - - // Attempts to match `v` against the pattern `p`, returning whether matching - // is successful. If it is, populates **bindings with the variables bound by - // the match; `bindings` should only be nullopt in contexts where `p` - // is not permitted to bind variables. **bindings may be modified even if the - // match is unsuccessful, so it should typically be created for the - // PatternMatch call and then merged into an existing scope on success. - [[nodiscard]] auto PatternMatch( - Nonnull p, Nonnull v, - SourceLocation source_loc, std::optional> bindings) - -> bool; - - // Support TypeChecker allocating values on the heap. - auto AllocateValue(Nonnull v) -> AllocationId { - return heap_.AllocateValue(v); - } - - private: - void Step(); - - // State transitions for expressions. - void StepExp(); - // State transitions for lvalues. - void StepLvalue(); - // State transitions for patterns. - void StepPattern(); - // State transition for statements. - void StepStmt(); - // State transition for declarations. - void StepDeclaration(); - - // Calls Step() repeatedly until there are no steps left to execute. Produces - // trace output if trace_steps is true. - void RunAllSteps(bool trace_steps); - - auto CreateStruct(const std::vector& fields, - const std::vector>& values) - -> Nonnull; - - auto EvalPrim(Operator op, const std::vector>& args, - SourceLocation source_loc) -> Nonnull; - - // Returns the result of converting `value` to type `destination_type`. - auto Convert(Nonnull value, - Nonnull destination_type) const - -> Nonnull; - - void PrintState(llvm::raw_ostream& out); - - // Runs `action` in an environment where the given constants are defined, and - // returns the result. `action` must produce a result. In other words, it must - // not be a StatementAction, ScopeAction, or DeclarationAction. Can only be - // called at compile time (before InterpProgram), and while `todo_` is empty. - auto RunCompileTimeAction(std::unique_ptr action) - -> Nonnull; - - Nonnull arena_; - - Heap heap_; - ActionStack todo_; - - // The underlying states of continuation values. All StackFragments created - // during execution are tracked here, in order to safely deallocate the - // contents of any non-completed continuations at the end of execution. - std::vector> stack_fragments_; - - bool trace_; -}; +// Attempts to match `v` against the pattern `p`, returning whether matching +// is successful. If it is, populates **bindings with the variables bound by +// the match; `bindings` should only be nullopt in contexts where `p` +// is not permitted to bind variables. **bindings may be modified even if the +// match is unsuccessful, so it should typically be created for the +// PatternMatch call and then merged into an existing scope on success. +// TODO: consider moving this to a separate header. +[[nodiscard]] auto PatternMatch(Nonnull p, + Nonnull v, + SourceLocation source_loc, + std::optional> bindings) + -> bool; } // namespace Carbon diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index e4c466d51360..da86664b7d60 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -404,8 +404,8 @@ void TypeChecker::TypeCheckExp(Nonnull e) { switch (aggregate_type.kind()) { case Value::Kind::TupleValue: { const auto& tuple_type = cast(aggregate_type); - int i = - cast(*interpreter_.InterpExp(&index.offset())).value(); + int i = cast(*InterpExp(&index.offset(), arena_, trace_)) + .value(); if (i < 0 || i >= static_cast(tuple_type.elements().size())) { FATAL_COMPILATION_ERROR(e->source_loc()) << "index " << i << " is out of range for type " << tuple_type; @@ -443,7 +443,7 @@ void TypeChecker::TypeCheckExp(Nonnull e) { for (auto& arg : struct_type.fields()) { TypeCheckExp(&arg.expression()); ExpectIsConcreteType(arg.expression().source_loc(), - interpreter_.InterpExp(&arg.expression())); + InterpExp(&arg.expression(), arena_, trace_)); } if (struct_type.fields().empty()) { // `{}` is the type of `{}`, just as `()` is the type of `()`. @@ -667,9 +667,9 @@ void TypeChecker::TypeCheckExp(Nonnull e) { case ExpressionKind::FunctionTypeLiteral: { auto& fn = cast(*e); ExpectIsConcreteType(fn.parameter().source_loc(), - interpreter_.InterpExp(&fn.parameter())); + InterpExp(&fn.parameter(), arena_, trace_)); ExpectIsConcreteType(fn.return_type().source_loc(), - interpreter_.InterpExp(&fn.return_type())); + InterpExp(&fn.return_type(), arena_, trace_)); SetStaticType(&fn, arena_->New()); fn.set_value_category(ValueCategory::Let); return; @@ -727,13 +727,14 @@ void TypeChecker::TypeCheckPattern( case PatternKind::BindingPattern: { auto& binding = cast(*p); TypeCheckPattern(&binding.type(), std::nullopt); - Nonnull type = interpreter_.InterpPattern(&binding.type()); + Nonnull type = + InterpPattern(&binding.type(), arena_, trace_); if (expected) { if (IsConcreteType(type)) { ExpectType(p->source_loc(), "name binding", type, *expected); } else { - if (!interpreter_.PatternMatch( - type, *expected, binding.type().source_loc(), std::nullopt)) { + if (!PatternMatch(type, *expected, binding.type().source_loc(), + std::nullopt)) { FATAL_COMPILATION_ERROR(binding.type().source_loc()) << "Type pattern '" << *type << "' does not match actual type '" << **expected << "'"; @@ -743,7 +744,7 @@ void TypeChecker::TypeCheckPattern( } ExpectIsConcreteType(binding.source_loc(), type); SetStaticType(&binding, type); - SetValue(&binding, interpreter_.InterpPattern(&binding)); + SetValue(&binding, InterpPattern(&binding, arena_, trace_)); return; } case PatternKind::TuplePattern: { @@ -767,7 +768,7 @@ void TypeChecker::TypeCheckPattern( field_types.push_back(&field->static_type()); } SetStaticType(&tuple, arena_->New(std::move(field_types))); - SetValue(&tuple, interpreter_.InterpPattern(&tuple)); + SetValue(&tuple, InterpPattern(&tuple, arena_, trace_)); return; } case PatternKind::AlternativePattern: { @@ -795,14 +796,14 @@ void TypeChecker::TypeCheckPattern( } TypeCheckPattern(&alternative.arguments(), *parameter_types); SetStaticType(&alternative, &choice_type); - SetValue(&alternative, interpreter_.InterpPattern(&alternative)); + SetValue(&alternative, InterpPattern(&alternative, arena_, trace_)); return; } case PatternKind::ExpressionPattern: { auto& expression = cast(*p).expression(); TypeCheckExp(&expression); SetStaticType(p, &expression.static_type()); - SetValue(p, interpreter_.InterpPattern(p)); + SetValue(p, InterpPattern(p, arena_, trace_)); return; } } @@ -1000,7 +1001,7 @@ void TypeChecker::TypeCheckFunctionDeclaration(Nonnull f, // new types into scope. TypeCheckExp(*return_expression); SetStaticType(&f->return_term(), - interpreter_.InterpExp(*return_expression)); + InterpExp(*return_expression, arena_, trace_)); } else if (f->return_term().is_omitted()) { SetStaticType(&f->return_term(), TupleValue::Empty()); } else { @@ -1065,7 +1066,7 @@ void TypeChecker::TypeCheckChoiceDeclaration( std::vector alternatives; for (Nonnull alternative : choice->alternatives()) { TypeCheckExp(&alternative->signature()); - auto signature = interpreter_.InterpExp(&alternative->signature()); + auto signature = InterpExp(&alternative->signature(), arena_, trace_); alternatives.push_back({.name = alternative->name(), .value = signature}); } auto ct = arena_->New(choice->name(), std::move(alternatives)); @@ -1108,7 +1109,7 @@ void TypeChecker::TypeCheckDeclaration(Nonnull d) { << "Type of a top-level variable must be an expression."; } Nonnull declared_type = - interpreter_.InterpExp(&binding_type->expression()); + InterpExp(&binding_type->expression(), arena_, trace_); SetStaticType(&var, declared_type); ExpectType(var.source_loc(), "initializer of variable", declared_type, &var.initializer().static_type()); @@ -1149,7 +1150,7 @@ void TypeChecker::TopLevel(Nonnull d) { Expression& type = cast(var.binding().type()).expression(); TypeCheckPattern(&var.binding(), std::nullopt); - Nonnull declared_type = interpreter_.InterpExp(&type); + Nonnull declared_type = InterpExp(&type, arena_, trace_); SetStaticType(&var, declared_type); break; } diff --git a/executable_semantics/interpreter/type_checker.h b/executable_semantics/interpreter/type_checker.h index 43628bc91535..522fbc68f0a0 100644 --- a/executable_semantics/interpreter/type_checker.h +++ b/executable_semantics/interpreter/type_checker.h @@ -20,7 +20,7 @@ namespace Carbon { class TypeChecker { public: explicit TypeChecker(Nonnull arena, bool trace) - : arena_(arena), interpreter_(arena, trace), trace_(trace) {} + : arena_(arena), trace_(trace) {} void TypeCheck(AST& ast); @@ -96,7 +96,6 @@ class TypeChecker { void PrintConstants(llvm::raw_ostream& out); Nonnull arena_; - Interpreter interpreter_; std::set constants_; bool trace_;