mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-06 10:44:41 +01:00
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:
committed by
GitHub
co-authored by
Dave Abrahams
Geoff Romer
parent
6df0d51516
commit
3fa72d2984
@@ -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:
|
||||
|
||||
@@ -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*;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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*;
|
||||
|
||||
Reference in New Issue
Block a user