Experimental control-flow operator (#368)

* AST and syntax for delimited control

* stashing for later

* a little more progress

* progress on delimited continuations

* delimit, suspend, and resume implemented (draft)

* example that generates the natural numbers

* fixes

* tinkering

* changed demo to experimental

* comments and name changes

* describe delimited continuations in the README

* renamed Snapshot to Continuation, edits to comments

* Update executable_semantics/ast/statement.h

improve comment for MakeDelimitStmt

Co-authored-by: Dave Abrahams <dabrahams@google.com>

* Update executable_semantics/interpreter/interpreter.cpp

remove snake_case

Co-authored-by: Dave Abrahams <dabrahams@google.com>

* edits to comments, change name of variable

* updates to handle review edits

* trailing whitespace

* fixes to delimited continuations, added more tests, also fixed assignment to do a copy

* improvements from Geoffrey

* new test from Geoffrey, fix for empty blocks

* more suggestions from Geoffrey

* more tests for delimited continuations, renaming some of them

* renamed test files

* improve a comment

* sketch of creating continuation

* initial implementation of shift/reset style continuations

* more documentation

* fix some camel case

* implemented deep copy of continuations, added a test case for it

* fixed a bug and got the recursive test case working

* removed __delimit, polished up __continuation

* back to shallow copy for continuations

* suggestions from Geoffrey

* removed structured binding (for now)

* Update executable_semantics/ast/expression.cpp

Co-authored-by: Geoff Romer <gromer@google.com>

* responses to Geoffrey

Co-authored-by: Dave Abrahams <dabrahams@google.com>
Co-authored-by: Geoff Romer <gromer@google.com>
This commit is contained in:
Jeremy G. Siek
2021-03-26 11:22:48 -04:00
committed by GitHub
co-authored by Dave Abrahams Geoff Romer
parent 6df0d51516
commit 3fa72d2984
35 changed files with 674 additions and 31 deletions
@@ -7,6 +7,7 @@
#include <cassert>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <optional>
#include <utility>
@@ -83,6 +84,9 @@ auto CopyVal(const Value* val, int line_num) -> const Value* {
return MakeFunVal(*val->u.fun.name, val->u.fun.param, val->u.fun.body);
case ValKind::PtrV:
return MakePtrVal(val->u.ptr);
case ValKind::ContinuationV:
// Copying a continuation is "shallow".
return val;
case ValKind::FunctionTV:
return MakeFunTypeVal(CopyVal(val->u.fun_type.param, line_num),
CopyVal(val->u.fun_type.ret, line_num));
@@ -99,6 +103,8 @@ auto CopyVal(const Value* val, int line_num) -> const Value* {
return MakeVarTypeVal(*val->u.var_type);
case ValKind::AutoTV:
return MakeAutoTypeVal();
case ValKind::ContinuationTV:
return MakeContinuationTypeVal();
case ValKind::TupleTV: {
auto new_fields = new VarValues();
for (auto& field : *val->u.tuple_type.fields) {
@@ -199,8 +205,10 @@ void PrintState(std::ostream& out) {
PrintStack(state->stack, out);
out << std::endl << "heap: ";
PrintHeap(state->heap, out);
out << std::endl << "env: ";
PrintEnv(CurrentEnv(state), out);
if (!state->stack.IsEmpty() && !state->stack.Top()->scopes.IsEmpty()) {
out << std::endl << "env: ";
PrintEnv(CurrentEnv(state), out);
}
out << std::endl << "}" << std::endl;
}
@@ -242,6 +250,20 @@ auto ValToPtr(const Value* v, int line_num) -> Address {
}
}
// Returns *continuation represented as a list of frames.
//
// - Precondition: continuation->tag == ValKind::ContinuationV.
auto ContinuationToVector(const Value* continuation, int sourceLocation)
-> std::vector<Frame*> {
if (continuation->tag == ValKind::ContinuationV) {
return *continuation->u.continuation.stack;
} else {
std::cerr << sourceLocation << ": runtime error: expected an integer"
<< std::endl;
exit(-1);
}
}
auto EvalPrim(Operator op, const std::vector<const Value*>& args, int line_num)
-> const Value* {
switch (op) {
@@ -278,7 +300,7 @@ void InitGlobals(std::list<Declaration>* fs) {
auto ChoiceDeclaration::InitGlobals(Env& globals) const -> void {
auto alts = new VarValues();
for (auto kv : alternatives) {
auto t = ToType(line_num, InterpExp(Env(), kv.second));
auto t = ToType(this->line_num, InterpExp(Env(), kv.second));
alts->push_back(make_pair(kv.first, t));
}
auto ct = MakeChoiceTypeVal(name, alts);
@@ -496,7 +518,7 @@ auto PatternMatch(const Value* p, const Value* v, Env env,
void PatternAssignment(const Value* pat, const Value* val, int line_num) {
switch (pat->tag) {
case ValKind::PtrV:
state->heap[ValToPtr(pat, line_num)] = val;
state->heap[ValToPtr(pat, line_num)] = CopyVal(val, line_num);
break;
case ValKind::TupleV: {
switch (val->tag) {
@@ -618,6 +640,7 @@ void StepLvalue() {
case ExpressionKind::TypeT:
case ExpressionKind::FunctionT:
case ExpressionKind::AutoT:
case ExpressionKind::ContinuationT:
case ExpressionKind::PatternVariable: {
frame->todo.Pop();
frame->todo.Push(MakeExpToLvalAct());
@@ -743,6 +766,12 @@ void StepExp() {
act->pos++;
break;
}
case ExpressionKind::ContinuationT: {
const Value* v = MakeContinuationTypeVal();
frame->todo.Pop(1);
frame->todo.Push(MakeValAct(v));
break;
}
} // switch (exp->tag)
}
@@ -826,10 +855,14 @@ void StepStmt() {
break;
case StatementKind::Block: {
if (act->pos == -1) {
auto* scope = new Scope(CurrentEnv(state), std::list<std::string>());
frame->scopes.Push(scope);
frame->todo.Push(MakeStmtAct(stmt->u.block.stmt));
act->pos++;
if (stmt->u.block.stmt) {
auto* scope = new Scope(CurrentEnv(state), {});
frame->scopes.Push(scope);
frame->todo.Push(MakeStmtAct(stmt->u.block.stmt));
act->pos++;
} else {
frame->todo.Pop();
}
} else {
Scope* scope = frame->scopes.Top();
KillScope(stmt->line_num, scope);
@@ -876,6 +909,43 @@ void StepStmt() {
}
frame->todo.Push(MakeStmtAct(stmt->u.sequence.stmt));
break;
case StatementKind::Continuation: {
// Create a continuation object by creating a frame similar the
// way one is created in a function call.
Scope* scope = new Scope(CurrentEnv(state), std::list<std::string>());
Stack<Scope*> scopes;
scopes.Push(scope);
Stack<Action*> todo;
todo.Push(
MakeStmtAct(MakeReturn(stmt->line_num, MakeUnit(stmt->line_num))));
todo.Push(MakeStmtAct(stmt->u.continuation.body));
Frame* continuation_frame = new Frame("__continuation", scopes, todo);
Address continuation_address =
AllocateValue(MakeContinuation({continuation_frame}));
// Store the continuation's address in the frame.
continuation_frame->continuation = continuation_address;
// Bind the continuation object to the continuation variable
frame->scopes.Top()->env.Set(*stmt->u.continuation.continuation_variable,
continuation_address);
// Pop the continuation statement.
frame->todo.Pop();
break;
}
case StatementKind::Run:
// Evaluate the argument of the run statement.
frame->todo.Push(MakeExpAct(stmt->u.run.argument));
act->pos++;
break;
case StatementKind::Await:
// Pause the current continuation
frame->todo.Pop();
std::vector<Frame*> paused;
do {
paused.push_back(state->stack.Pop());
} while (!paused.back()->IsContinuation());
// Update the continuation with the paused stack.
state->heap[paused.back()->continuation] = MakeContinuation(paused);
break;
}
}
@@ -1156,6 +1226,7 @@ void HandleValue() {
case ExpressionKind::BoolT:
case ExpressionKind::TypeT:
case ExpressionKind::AutoT:
case ExpressionKind::ContinuationT:
std::cerr << "internal error, bad expression context in handle_value"
<< std::endl;
exit(-1);
@@ -1311,6 +1382,25 @@ void HandleValue() {
frame->todo.Push(MakeValAct(ret_val));
break;
}
case StatementKind::Run: {
frame->todo.Pop(2);
// Push an expression statement action to ignore the result
// value from the continuation.
Action* ignore_result = MakeStmtAct(
MakeExpStmt(stmt->line_num, MakeUnit(stmt->line_num)));
ignore_result->pos = 0;
frame->todo.Push(ignore_result);
// Push the continuation onto the current stack.
std::vector<Frame*> continuation_vector =
ContinuationToVector(val_act->u.val, stmt->line_num);
for (auto frame_iter = continuation_vector.rbegin();
frame_iter != continuation_vector.rend(); ++frame_iter) {
state->stack.Push(*frame_iter);
}
break;
}
case StatementKind::Continuation:
case StatementKind::Await:
case StatementKind::Block:
case StatementKind::Sequence:
case StatementKind::Break:
+23 -1
View File
@@ -29,13 +29,33 @@ struct Scope {
/***** Frames and State *****/
// A frame represents either a function call or a delimited continuation.
struct Frame {
// The name of the function.
std::string name;
// If the frame represents a function call, the bottom scope
// contains the parameter-argument bindings for this function
// call. The rest of the scopes contain local variables defined by
// blocks within the function. The scope at the top of the stack is
// the current scope and its environment is the one used for looking
// up the value associated with a variable.
Stack<Scope*> scopes;
// The actions that need to be executed in the future of the
// current function call. The top of the stack is the action
// that is executed first.
Stack<Action*> todo;
// If this frame is the bottom frame of a continuation, then it stores
// the address of the continuation.
// Otherwise the `continuation` field is the sentinel UINT_MAX.
Address continuation;
// Returns whether this frame is the bottom frame of a continuation.
auto IsContinuation() -> bool { return continuation != UINT_MAX; }
Frame(std::string n, Stack<Scope*> s, Stack<Action*> c)
: name(std::move(std::move(n))), scopes(s), todo(c) {}
: name(std::move(std::move(n))),
scopes(s),
todo(c),
continuation(UINT_MAX) {}
};
struct State {
@@ -46,6 +66,8 @@ struct State {
extern State* state;
auto PrintFrame(Frame* frame, std::ostream& out) -> void;
void PrintStack(Stack<Frame*> ls, std::ostream& out);
void PrintEnv(Env env);
auto AllocateValue(const Value* v) -> Address;
auto CopyVal(const Value* val, int line_num) -> const Value*;
+36 -2
View File
@@ -76,6 +76,7 @@ auto ToType(int line_num, const Value* val) -> const Value* {
case ValKind::BoolTV:
case ValKind::IntTV:
case ValKind::AutoTV:
case ValKind::ContinuationTV:
return val;
default:
std::cerr << line_num << ": in ToType, expected a type, not ";
@@ -96,6 +97,8 @@ auto ReifyType(const Value* t, int line_num) -> Expression* {
return MakeBoolType(0);
case ValKind::TypeTV:
return MakeTypeType(0);
case ValKind::ContinuationTV:
return MakeContinuationType(0);
case ValKind::FunctionTV:
return MakeFunType(0, ReifyType(t->u.fun_type.param, line_num),
ReifyType(t->u.fun_type.ret, line_num));
@@ -382,10 +385,15 @@ auto TypeCheckExp(Expression* e, TypeEnv env, Env ct_env, const Value* expected,
}
}
case ExpressionKind::IntT:
return TCResult(e, MakeIntTypeVal(), env);
case ExpressionKind::BoolT:
return TCResult(e, MakeBoolTypeVal(), env);
case ExpressionKind::TypeT:
case ExpressionKind::AutoT:
return TCResult(e, MakeTypeTypeVal(), env);
case ExpressionKind::AutoT:
return TCResult(e, MakeAutoTypeVal(), env);
case ExpressionKind::ContinuationT:
return TCResult(e, MakeContinuationTypeVal(), env);
}
}
@@ -500,7 +508,29 @@ auto TypeCheckStmt(Statement* s, TypeEnv env, Env ct_env,
}
return TCStatement(MakeReturn(s->line_num, res.exp), env);
}
}
case StatementKind::Continuation: {
TCStatement body_result =
TypeCheckStmt(s->u.continuation.body, env, ct_env, ret_type);
Statement* new_continuation = MakeContinuationStatement(
s->line_num, *s->u.continuation.continuation_variable,
body_result.stmt);
env.Set(*s->u.continuation.continuation_variable,
MakeContinuationTypeVal());
return TCStatement(new_continuation, env);
}
case StatementKind::Run: {
TCResult argument_result = TypeCheckExp(s->u.run.argument, env, ct_env,
nullptr, TCContext::ValueContext);
ExpectType(s->line_num, "argument of `run`", MakeContinuationTypeVal(),
argument_result.type);
Statement* new_run = MakeRun(s->line_num, argument_result.exp);
return TCStatement(new_run, env);
}
case StatementKind::Await: {
// nothing to do here
return TCStatement(s, env);
}
} // switch
}
auto CheckOrEnsureReturn(Statement* stmt, bool void_return, int line_num)
@@ -547,6 +577,10 @@ auto CheckOrEnsureReturn(Statement* stmt, bool void_return, int line_num)
return CheckOrEnsureReturn(stmt->u.sequence.stmt, void_return,
stmt->line_num);
}
case StatementKind::Continuation:
case StatementKind::Run:
case StatementKind::Await:
return stmt;
case StatementKind::Assign:
case StatementKind::ExpressionStatement:
case StatementKind::While:
@@ -103,6 +103,15 @@ auto MakeAltCons(std::string alt_name, std::string choice_name)
return v;
}
// Return a first-class continuation represented a fragment
// of the stack.
auto MakeContinuation(std::vector<Frame*> stack) -> Value* {
auto* v = new Value();
v->tag = ValKind::ContinuationV;
v->u.continuation.stack = new std::vector<Frame*>(stack);
return v;
}
auto MakeVarPatVal(std::string name, const Value* type) -> const Value* {
auto* v = new Value();
v->tag = ValKind::VarPatV;
@@ -136,6 +145,13 @@ auto MakeTypeTypeVal() -> const Value* {
return v;
}
// Return a Continuation type.
auto MakeContinuationTypeVal() -> const Value* {
auto* v = new Value();
v->tag = ValKind::ContinuationTV;
return v;
}
auto MakeAutoTypeVal() -> const Value* {
auto* v = new Value();
v->tag = ValKind::AutoTV;
@@ -255,6 +271,9 @@ void PrintValue(const Value* val, std::ostream& out) {
case ValKind::AutoTV:
out << "auto";
break;
case ValKind::ContinuationTV:
out << "Continuation";
break;
case ValKind::PointerTV:
out << "Ptr(";
PrintValue(val->u.ptr_type.type, out);
@@ -291,6 +310,14 @@ void PrintValue(const Value* val, std::ostream& out) {
case ValKind::ChoiceTV:
out << "choice " << *val->u.choice_type.name;
break;
case ValKind::ContinuationV:
out << "continuation[[";
for (Frame* frame : *val->u.continuation.stack) {
PrintFrame(frame, out);
out << " :: ";
}
out << "]]";
break;
}
}
@@ -314,6 +341,7 @@ auto TypeEqual(const Value* t1, const Value* t2) -> bool {
return FieldsEqual(t1->u.tuple_type.fields, t2->u.tuple_type.fields);
case ValKind::IntTV:
case ValKind::BoolTV:
case ValKind::ContinuationTV:
return true;
default:
return false;
+28 -1
View File
@@ -9,6 +9,7 @@
#include <vector>
#include "executable_semantics/ast/statement.h"
#include "executable_semantics/interpreter/stack.h"
namespace Carbon {
@@ -38,69 +39,94 @@ enum class ValKind {
TupleTV,
StructTV,
ChoiceTV,
ContinuationTV, // The type of a continuation.
VarPatV,
AltConsV
AltConsV,
ContinuationV // A first-class continuation value.
};
struct Frame; // used by continuation
struct Value {
ValKind tag;
union {
int integer;
bool boolean;
struct {
std::string* name;
const Value* param;
Statement* body;
} fun;
struct {
const Value* type;
const Value* inits;
} struct_val;
struct {
std::string* alt_name;
std::string* choice_name;
} alt_cons;
struct {
std::string* alt_name;
std::string* choice_name;
Address argument;
} alt;
struct {
std::vector<std::pair<std::string, Address>>* elts;
} tuple;
Address ptr;
std::string* var_type;
struct {
std::string* name;
const Value* type;
} var_pat;
struct {
const Value* param;
const Value* ret;
} fun_type;
struct {
const Value* type;
} ptr_type;
struct {
std::string* name;
VarValues* fields;
VarValues* methods;
} struct_type;
struct {
std::string* name;
VarValues* fields;
} tuple_type;
struct {
std::string* name;
VarValues* alternatives;
} choice_type;
struct {
std::list<std::string*>* params;
const Value* type;
} implicit;
struct {
std::vector<Frame*>* stack;
} continuation;
} u;
};
// Return a first-class continuation represented by the
// given stack, down to the nearest enclosing `__continuation`.
auto MakeContinuation(std::vector<Frame*> stack) -> Value*;
auto MakeIntVal(int i) -> const Value*;
auto MakeBoolVal(bool b) -> const Value*;
auto MakeFunVal(std::string name, const Value* param, Statement* body)
@@ -117,6 +143,7 @@ auto MakeVarPatVal(std::string name, const Value* type) -> const Value*;
auto MakeVarTypeVal(std::string name) -> const Value*;
auto MakeIntTypeVal() -> const Value*;
auto MakeContinuationTypeVal() -> const Value*;
auto MakeAutoTypeVal() -> const Value*;
auto MakeBoolTypeVal() -> const Value*;
auto MakeTypeTypeVal() -> const Value*;