From 79e3d284b4e20bb130d3cdc2604e21e3601c479f Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Wed, 20 Oct 2021 10:05:17 -0700 Subject: [PATCH] Move pattern interpretation to compile time (#904) Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com> --- executable_semantics/ast/pattern.h | 13 ++++ .../interpreter/interpreter.cpp | 63 +++++++------------ .../interpreter/interpreter.h | 2 +- .../interpreter/type_checker.cpp | 14 +++++ executable_semantics/interpreter/value.cpp | 6 +- executable_semantics/interpreter/value.h | 18 ++---- 6 files changed, 58 insertions(+), 58 deletions(-) diff --git a/executable_semantics/ast/pattern.h b/executable_semantics/ast/pattern.h index 90587c03bf89..be60e9e1f4f5 100644 --- a/executable_semantics/ast/pattern.h +++ b/executable_semantics/ast/pattern.h @@ -60,6 +60,18 @@ class Pattern { // and after typechecking it's guaranteed to be true. auto has_static_type() const -> bool { return static_type_.has_value(); } + // The value of this pattern. Cannot be called before typechecking. + auto value() const -> const Value& { return **value_; } + + // Sets the value of this pattern. Can only be called once, during + // typechecking. + void set_value(Nonnull value) { value_ = value; } + + // Returns whether the value has been set. Should only be called + // during typechecking: before typechecking it's guaranteed to be false, + // and after typechecking it's guaranteed to be true. + auto has_value() const -> bool { return value_.has_value(); } + protected: // Constructs a Pattern representing syntax at the given line number. // `kind` must be the enumerator corresponding to the most-derived type being @@ -72,6 +84,7 @@ class Pattern { SourceLocation source_loc_; std::optional> static_type_; + std::optional> value_; }; // A pattern consisting of the `auto` keyword. diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index f9e286c8fd8a..55230cda01fe 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -116,8 +116,7 @@ void Interpreter::InitEnv(const Declaration& d, Env* env) { heap_.AllocateValue(arena_->New(deduced.name)); new_env.Set(deduced.name, a); } - auto pt = InterpPattern(new_env, &func_def.param_pattern()); - auto f = arena_->New(func_def.name(), pt, func_def.body()); + Nonnull f = arena_->New(&func_def); Address a = heap_.AllocateValue(f); env->Set(func_def.name(), a); break; @@ -585,10 +584,8 @@ auto Interpreter::StepExp() -> Transition { } case Value::Kind::FunctionValue: return CallFunction{ - // TODO: Think about a cleaner way to cast between Ptr types. - // (multiple TODOs) - .function = Nonnull( - cast(act->results()[0])), + .function = + &cast(*act->results()[0]).declaration(), .args = act->results()[1], .source_loc = exp.source_loc()}; default: @@ -755,44 +752,26 @@ auto Interpreter::StepStmt() -> Transition { frame->scopes.Push(arena_->New(CurrentEnv())); return Spawn{arena_->New(&match_stmt.expression())}; } else { - // Regarding act->pos(): - // * odd: start interpreting the pattern of a clause - // * even: finished interpreting the pattern, now try to match - // - // Regarding act->results(): - // * 0: the value that we're matching - // * 1: the pattern for clause 0 - // * 2: the pattern for clause 1 - // * ... - auto clause_num = (act->pos() - 1) / 2; + int clause_num = act->pos() - 1; if (clause_num >= static_cast(match_stmt.clauses().size())) { DeallocateScope(frame->scopes.Top()); frame->scopes.Pop(); return Done{}; } auto c = match_stmt.clauses()[clause_num]; + std::optional matches = PatternMatch( + &c.pattern().value(), act->results()[0], stmt.source_loc()); + if (matches) { // We have a match, start the body. + // Ensure we don't process any more clauses. + act->set_pos(match_stmt.clauses().size() + 1); - if (act->pos() % 2 == 1) { - // start interpreting the pattern of the clause - // { {v :: (match ([]) ...) :: C, E, F} :: S, H} - // -> { {pi :: (match ([]) ...) :: C, E, F} :: S, H} - return Spawn{arena_->New(&c.pattern())}; - } else { // try to match - auto v = act->results()[0]; - auto pat = act->results()[clause_num + 1]; - std::optional matches = PatternMatch(pat, v, stmt.source_loc()); - if (matches) { // we have a match, start the body - // Ensure we don't process any more clauses. - act->set_pos(2 * match_stmt.clauses().size() + 1); - - for (const auto& [name, value] : *matches) { - frame->scopes.Top()->values.Set(name, value); - frame->scopes.Top()->locals.push_back(name); - } - return Spawn{arena_->New(&c.statement())}; - } else { - return RunAgain{}; + for (const auto& [name, value] : *matches) { + frame->scopes.Top()->values.Set(name, value); + frame->scopes.Top()->locals.push_back(name); } + return Spawn{arena_->New(&c.statement())}; + } else { + return RunAgain{}; } } } @@ -859,14 +838,12 @@ auto Interpreter::StepStmt() -> Transition { // -> { {e :: (var x = []) :: C, E, F} :: S, H} return Spawn{arena_->New( &cast(stmt).init())}; - } else if (act->pos() == 1) { - return Spawn{arena_->New( - &cast(stmt).pattern())}; } else { // { { v :: (x = []) :: C, E, F} :: S, H} // -> { { C, E(x := a), F} :: S, H(a := copy(v))} Nonnull v = act->results()[0]; - Nonnull p = act->results()[1]; + Nonnull p = + &cast(stmt).pattern().value(); std::optional matches = PatternMatch(p, v, stmt.source_loc()); CHECK(matches) @@ -1079,7 +1056,7 @@ class Interpreter::DoTransition { void operator()(const CallFunction& call) { interpreter->stack_.Top()->todo.Pop(); std::optional matches = interpreter->PatternMatch( - &call.function->parameters(), call.args, call.source_loc); + &call.function->param_pattern().value(), call.args, call.source_loc); CHECK(matches.has_value()) << "internal error in call_function, pattern match failed"; // Create the new frame and push it on the stack @@ -1153,6 +1130,10 @@ auto Interpreter::InterpProgram(llvm::ArrayRef> fs, } while (stack_.Count() > 1 || !stack_.Top()->todo.IsEmpty()) { + if (!stack_.Top()->todo.IsEmpty()) { + CHECK(stack_.Top()->todo.Top()->kind() != Action::Kind::PatternAction) + << "Pattern evaluation must happen before run-time."; + } Step(); if (trace_) { PrintState(llvm::outs()); diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index b493ce60bc91..c3303e53447c 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -101,7 +101,7 @@ class Interpreter { // stack, then creates a new stack frame which calls the specified function // with the specified arguments. struct CallFunction { - Nonnull function; + Nonnull function; Nonnull args; SourceLocation source_loc; }; diff --git a/executable_semantics/interpreter/type_checker.cpp b/executable_semantics/interpreter/type_checker.cpp index 9c15a0927d2f..725cfb63700c 100644 --- a/executable_semantics/interpreter/type_checker.cpp +++ b/executable_semantics/interpreter/type_checker.cpp @@ -58,6 +58,16 @@ static void SetStaticType(Nonnull definition, } } +static void SetValue(Nonnull pattern, Nonnull value) { + // TODO: find some way to CHECK that `value` is identical to pattern->value(), + // if it's already set. Unclear if `ValueEqual` is suitable, because it + // currently focuses more on "real" values, and disallows the pseudo-values + // like `BindingPlaceholderValue` that we get in pattern evaluation. + if (!pattern->has_value()) { + pattern->set_value(value); + } +} + TypeChecker::ReturnTypeContext::ReturnTypeContext( Nonnull orig_return_type, bool is_omitted) : is_auto_(isa(orig_return_type)), @@ -749,6 +759,7 @@ auto TypeChecker::TypeCheckPattern( types.Set(*binding.name(), type); } SetStaticType(&binding, type); + SetValue(&binding, interpreter_.InterpPattern(values, &binding)); return TCResult(types); } case Pattern::Kind::TuplePattern: { @@ -775,6 +786,7 @@ auto TypeChecker::TypeCheckPattern( field_types.push_back(&field->static_type()); } SetStaticType(&tuple, arena_->New(std::move(field_types))); + SetValue(&tuple, interpreter_.InterpPattern(values, &tuple)); return TCResult(new_types); } case Pattern::Kind::AlternativePattern: { @@ -800,12 +812,14 @@ auto TypeChecker::TypeCheckPattern( TCResult arg_results = TypeCheckPattern(&alternative.arguments(), types, values, *parameter_types); SetStaticType(&alternative, choice_type); + SetValue(&alternative, interpreter_.InterpPattern(values, &alternative)); return TCResult(arg_results.types); } case Pattern::Kind::ExpressionPattern: { auto& expression = cast(*p).expression(); TCResult result = TypeCheckExp(&expression, types, values); SetStaticType(p, &expression.static_type()); + SetValue(p, interpreter_.InterpPattern(values, p)); return TCResult(result.types); } } diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index 4004013b562a..1e197097c44d 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -212,7 +212,7 @@ void Value::Print(llvm::raw_ostream& out) const { out << (cast(*this).value() ? "true" : "false"); break; case Value::Kind::FunctionValue: - out << "fun<" << cast(*this).name() << ">"; + out << "fun<" << cast(*this).declaration().name() << ">"; break; case Value::Kind::PointerValue: out << "ptr<" << cast(*this).value() << ">"; @@ -392,9 +392,9 @@ auto ValueEqual(Nonnull v1, Nonnull v2, return cast(*v1).value() == cast(*v2).value(); case Value::Kind::FunctionValue: { std::optional> body1 = - cast(*v1).body(); + cast(*v1).declaration().body(); std::optional> body2 = - cast(*v2).body(); + cast(*v2).declaration().body(); return body1.has_value() == body2.has_value() && (!body1.has_value() || *body1 == *body2); } diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index 74198ef046a0..c3172400de3d 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -126,27 +126,19 @@ class IntValue : public Value { // A function value. class FunctionValue : public Value { public: - FunctionValue(std::string name, Nonnull parameters, - std::optional> body) - : Value(Kind::FunctionValue), - name_(std::move(name)), - parameters_(parameters), - body_(body) {} + FunctionValue(Nonnull declaration) + : Value(Kind::FunctionValue), declaration_(declaration) {} static auto classof(const Value* value) -> bool { return value->kind() == Kind::FunctionValue; } - auto name() const -> const std::string& { return name_; } - auto parameters() const -> const Value& { return *parameters_; } - auto body() const -> std::optional> { - return body_; + auto declaration() const -> const FunctionDeclaration& { + return *declaration_; } private: - std::string name_; - Nonnull parameters_; - std::optional> body_; + Nonnull declaration_; }; // A pointer value.