diff --git a/executable_semantics/BUILD b/executable_semantics/BUILD index d6a552e53a2e..d8f0fd8b2ed2 100644 --- a/executable_semantics/BUILD +++ b/executable_semantics/BUILD @@ -75,6 +75,7 @@ EXAMPLES = [ "experimental_continuation6", "experimental_continuation7", "experimental_continuation8", + "experimental_continuation9", ] [genrule( diff --git a/executable_semantics/interpreter/interpreter.cpp b/executable_semantics/interpreter/interpreter.cpp index bcb5cf8ecaaa..785ff8d19ddb 100644 --- a/executable_semantics/interpreter/interpreter.cpp +++ b/executable_semantics/interpreter/interpreter.cpp @@ -31,32 +31,52 @@ void HandleValue(); // Auxiliary Functions // -auto AllocateValue(const Value* v) -> Address { +auto State::AllocateValue(const Value* v) -> Address { // Putting the following two side effects together in this function // ensures that we don't do anything else in between, which is really bad! // Consider whether to include a copy of the input v in this function // or to leave it up to the caller. - Address a = state->heap.size(); - state->heap.push_back(new Value(*v)); - state->alive.push_back(true); + Address a = heap.size(); + heap.push_back(new Value(*v)); + this->alive.push_back(true); return a; } +auto State::ReadFromMemory(Address a, int line_num) -> const Value* { + this->CheckAlive(a, line_num); + return heap[a]; +} + +auto State::WriteToMemory(Address a, const Value* v, int line_num) -> void { + this->CheckAlive(a, line_num); + heap[a] = v; +} + +void State::CheckAlive(Address address, int line_num) { + if (!this->alive[address]) { + std::cerr << line_num << ": undefined behavior: access to dead value "; + PrintValue(heap[address], std::cerr); + std::cerr << std::endl; + exit(-1); + } +} + auto CopyVal(const Value* val, int line_num) -> const Value* { switch (val->tag) { case ValKind::TupleV: { auto elts = new std::vector>(); for (auto& i : *val->u.tuple.elts) { - CheckAlive(i.second, line_num); - const Value* elt = CopyVal(state->heap[i.second], line_num); - Address new_address = AllocateValue(elt); + const Value* elt = + CopyVal(state->ReadFromMemory(i.second, line_num), line_num); + Address new_address = state->AllocateValue(elt); elts->push_back(make_pair(i.first, new_address)); } return MakeTupleVal(elts); } case ValKind::AltV: { - const Value* arg = CopyVal(state->heap[val->u.alt.argument], line_num); - Address argument_address = AllocateValue(arg); + const Value* arg = CopyVal( + state->ReadFromMemory(val->u.alt.argument, line_num), line_num); + Address argument_address = state->AllocateValue(arg); return MakeAltVal(*val->u.alt.alt_name, *val->u.alt.choice_name, argument_address); } @@ -102,20 +122,18 @@ auto CopyVal(const Value* val, int line_num) -> const Value* { } } -void KillObject(Address address); - // Marks all of the sub-objects of this value as dead. void KillSubObjects(const Value* val) { switch (val->tag) { case ValKind::AltV: - KillObject(val->u.alt.argument); + state->KillObject(val->u.alt.argument); break; case ValKind::StructV: KillSubObjects(val->u.struct_val.inits); break; case ValKind::TupleV: for (auto& elt : *val->u.tuple.elts) { - KillObject(elt.second); + state->KillObject(elt.second); } break; default: @@ -123,11 +141,10 @@ void KillSubObjects(const Value* val) { } } -// Marks the object at this address, and all of its sub-objects, as dead. -void KillObject(Address address) { - if (state->alive[address]) { - state->alive[address] = false; - KillSubObjects(state->heap[address]); +void State::KillObject(Address address) { + if (this->alive[address]) { + this->alive[address] = false; + KillSubObjects(heap[address]); } else { std::cerr << "runtime error, killing an already dead value" << std::endl; exit(-1); @@ -135,9 +152,9 @@ void KillObject(Address address) { } void PrintEnv(Env values, std::ostream& out) { - for (const auto& [name, value] : values) { + for (const auto& [name, address] : values) { out << name << ": "; - PrintValue(state->heap[value], out); + state->PrintAddress(address, out); out << ", "; } } @@ -163,7 +180,7 @@ void PrintStack(Stack ls, std::ostream& out) { } } -void PrintHeap(const std::vector& heap, std::ostream& out) { +void State::PrintHeap(std::ostream& out) { for (auto& iter : heap) { if (iter) { PrintValue(iter, out); @@ -184,7 +201,7 @@ void PrintState(std::ostream& out) { out << "stack: "; PrintStack(state->stack, out); out << std::endl << "heap: "; - PrintHeap(state->heap, out); + state->PrintHeap(out); if (!state->stack.IsEmpty() && !state->stack.Top()->scopes.IsEmpty()) { out << std::endl << "values: "; PrintEnv(CurrentEnv(state), out); @@ -218,7 +235,6 @@ auto ValToBool(const Value* v, int line_num) -> int { } auto ValToPtr(const Value* v, int line_num) -> Address { - CheckAlive(v->u.ptr, line_num); switch (v->tag) { case ValKind::PtrV: return v->u.ptr; @@ -284,7 +300,7 @@ auto ChoiceDeclaration::InitGlobals(Env& globals) const -> void { alts->push_back(make_pair(kv.first, t)); } auto ct = MakeChoiceTypeVal(name, alts); - auto a = AllocateValue(ct); + auto a = state->AllocateValue(ct); globals.Set(name, a); } @@ -302,7 +318,7 @@ auto StructDeclaration::InitGlobals(Env& globals) const -> void { } } auto st = MakeStructTypeVal(*definition.name, fields, methods); - auto a = AllocateValue(st); + auto a = state->AllocateValue(st); globals.Set(*definition.name, a); } @@ -310,7 +326,7 @@ auto FunctionDeclaration::InitGlobals(Env& globals) const -> void { Env values; auto pt = InterpExp(values, definition->param_pattern); auto f = MakeFunVal(definition->name, pt, definition->body); - Address a = AllocateValue(f); + Address a = state->AllocateValue(f); globals.Set(definition->name, a); } @@ -318,7 +334,7 @@ auto FunctionDeclaration::InitGlobals(Env& globals) const -> void { // result of evaluating the initializer. auto VariableDeclaration::InitGlobals(Env& globals) const -> void { auto v = InterpExp(globals, initializer); - Address a = AllocateValue(v); + Address a = state->AllocateValue(v); globals.Set(name, a); } @@ -355,9 +371,9 @@ void CallFunction(int line_num, std::vector operas, } case ValKind::AltConsV: { const Value* arg = CopyVal(operas[1], line_num); - const Value* av = - MakeAltVal(*operas[0]->u.alt_cons.alt_name, - *operas[0]->u.alt_cons.choice_name, AllocateValue(arg)); + const Value* av = MakeAltVal(*operas[0]->u.alt_cons.alt_name, + *operas[0]->u.alt_cons.choice_name, + state->AllocateValue(arg)); Frame* frame = state->stack.Top(); frame->todo.Push(MakeValAct(av)); break; @@ -377,7 +393,7 @@ void KillScope(int line_num, Scope* scope) { std::cerr << "internal error in KillScope" << std::endl; exit(-1); } - KillObject(*a); + state->KillObject(*a); } } @@ -393,7 +409,7 @@ void CreateTuple(Frame* frame, Action* act, const Expression* /*exp*/) { auto elts = new std::vector>(); auto f = act->u.exp->u.tuple.fields->begin(); for (auto i = act->results.begin(); i != act->results.end(); ++i, ++f) { - Address a = AllocateValue(*i); // copy? + Address a = state->AllocateValue(*i); // copy? elts->push_back(make_pair(f->first, a)); } const Value* tv = MakeTupleVal(elts); @@ -411,7 +427,7 @@ auto PatternMatch(const Value* p, const Value* v, Env values, -> std::optional { switch (p->tag) { case ValKind::VarPatV: { - Address a = AllocateValue(CopyVal(v, line_num)); + Address a = state->AllocateValue(CopyVal(v, line_num)); vars->push_back(*p->u.var_pat.name); values.Set(*p->u.var_pat.name, a); return values; @@ -432,9 +448,9 @@ auto PatternMatch(const Value* p, const Value* v, Env values, std::cerr << std::endl; exit(-1); } - std::optional matches = - PatternMatch(state->heap[elt.second], state->heap[*a], values, - vars, line_num); + std::optional matches = PatternMatch( + state->ReadFromMemory(elt.second, line_num), + state->ReadFromMemory(*a, line_num), values, vars, line_num); if (!matches) { return std::nullopt; } @@ -456,9 +472,10 @@ auto PatternMatch(const Value* p, const Value* v, Env values, *p->u.alt.alt_name != *v->u.alt.alt_name) { return std::nullopt; } - std::optional matches = PatternMatch( - state->heap[p->u.alt.argument], state->heap[v->u.alt.argument], - values, vars, line_num); + std::optional matches = + PatternMatch(state->ReadFromMemory(p->u.alt.argument, line_num), + state->ReadFromMemory(v->u.alt.argument, line_num), + values, vars, line_num); if (!matches) { return std::nullopt; } @@ -498,7 +515,8 @@ auto PatternMatch(const Value* p, const Value* v, Env values, void PatternAssignment(const Value* pat, const Value* val, int line_num) { switch (pat->tag) { case ValKind::PtrV: - state->heap[ValToPtr(pat, line_num)] = CopyVal(val, line_num); + state->WriteToMemory(ValToPtr(pat, line_num), CopyVal(val, line_num), + line_num); break; case ValKind::TupleV: { switch (val->tag) { @@ -516,8 +534,8 @@ void PatternAssignment(const Value* pat, const Value* val, int line_num) { std::cerr << std::endl; exit(-1); } - PatternAssignment(state->heap[elt.second], state->heap[*a], - line_num); + PatternAssignment(state->ReadFromMemory(elt.second, line_num), + state->ReadFromMemory(*a, line_num), line_num); } break; } @@ -539,8 +557,9 @@ void PatternAssignment(const Value* pat, const Value* val, int line_num) { std::cerr << "internal error in pattern assignment" << std::endl; exit(-1); } - PatternAssignment(state->heap[pat->u.alt.argument], - state->heap[val->u.alt.argument], line_num); + PatternAssignment( + state->ReadFromMemory(pat->u.alt.argument, line_num), + state->ReadFromMemory(val->u.alt.argument, line_num), line_num); break; } default: @@ -584,7 +603,6 @@ void StepLvalue() { exit(-1); } const Value* v = MakePtrVal(*pointer); - CheckAlive(*pointer, exp->line_num); frame->todo.Pop(); frame->todo.Push(MakeValAct(v)); break; @@ -681,7 +699,7 @@ void StepExp() { << *(exp->u.variable.name) << "`" << std::endl; exit(-1); } - const Value* pointee = state->heap[*pointer]; + const Value* pointee = state->ReadFromMemory(*pointer, exp->line_num); frame->todo.Pop(1); frame->todo.Push(MakeValAct(pointee)); break; @@ -901,7 +919,7 @@ void StepStmt() { todo.Push(MakeStmtAct(stmt->u.continuation.body)); Frame* continuation_frame = new Frame("__continuation", scopes, todo); Address continuation_address = - AllocateValue(MakeContinuation({continuation_frame})); + state->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 @@ -924,13 +942,14 @@ void StepStmt() { paused.push_back(state->stack.Pop()); } while (!paused.back()->IsContinuation()); // Update the continuation with the paused stack. - state->heap[paused.back()->continuation] = MakeContinuation(paused); + state->WriteToMemory(paused.back()->continuation, + MakeContinuation(paused), stmt->line_num); break; } } -auto GetMember(Address a, const std::string& f) -> Address { - const Value* v = state->heap[a]; +auto GetMember(Address a, const std::string& f, int line_num) -> Address { + const Value* v = state->ReadFromMemory(a, line_num); switch (v->tag) { case ValKind::StructV: { auto a = FindTupleField(f, v->u.struct_val.inits); @@ -960,7 +979,7 @@ auto GetMember(Address a, const std::string& f) -> Address { exit(-1); } auto ac = MakeAltCons(f, *v->u.choice_type.name); - return AllocateValue(ac); + return state->AllocateValue(ac); } default: std::cerr << "field access not allowed for value "; @@ -1013,13 +1032,13 @@ void HandleValue() { } switch (act->tag) { case ActionKind::DeleteTmpAction: { - KillObject(act->u.delete_tmp); + state->KillObject(act->u.delete_tmp); frame->todo.Pop(2); frame->todo.Push(val_act); break; } case ActionKind::ExpToLValAction: { - Address a = AllocateValue(act->results[0]); + Address a = state->AllocateValue(act->results[0]); auto del = MakeDeleteAct(a); frame->todo.Pop(2); InsertDelete(del, frame->todo); @@ -1033,8 +1052,8 @@ void HandleValue() { // { v :: [].f :: C, E, F} :: S, H} // -> { { &v.f :: C, E, F} :: S, H } const Value* str = act->results[0]; - Address a = - GetMember(ValToPtr(str, exp->line_num), *exp->u.get_field.field); + Address a = GetMember(ValToPtr(str, exp->line_num), + *exp->u.get_field.field, exp->line_num); frame->todo.Pop(2); frame->todo.Push(MakeValAct(MakePtrVal(a))); break; @@ -1126,7 +1145,8 @@ void HandleValue() { exit(-1); } frame->todo.Pop(2); - frame->todo.Push(MakeValAct(state->heap[*a])); + const Value* element = state->ReadFromMemory(*a, exp->line_num); + frame->todo.Push(MakeValAct(element)); break; } default: @@ -1143,9 +1163,10 @@ void HandleValue() { // { { v :: [].f :: C, E, F} :: S, H} // -> { { v_f :: C, E, F} : S, H} auto a = GetMember(ValToPtr(act->results[0], exp->line_num), - *exp->u.get_field.field); + *exp->u.get_field.field, exp->line_num); + const Value* element = state->ReadFromMemory(a, exp->line_num); frame->todo.Pop(2); - frame->todo.Push(MakeValAct(state->heap[a])); + frame->todo.Push(MakeValAct(element)); break; } case ExpressionKind::PrimitiveOp: { diff --git a/executable_semantics/interpreter/interpreter.h b/executable_semantics/interpreter/interpreter.h index f7b972ee24a5..f97a5170b7bf 100644 --- a/executable_semantics/interpreter/interpreter.h +++ b/executable_semantics/interpreter/interpreter.h @@ -59,10 +59,30 @@ struct Frame { continuation(UINT_MAX) {} }; +// TODO(geoffromer): Make this a class, with all members private struct State { Stack stack; - std::vector heap; std::vector alive; + + // Returns the value at the given address in the heap after + // checking that it is alive. + auto ReadFromMemory(Address a, int line_num) -> const Value*; + // Writes the given value at the address in the heap after + // checking that the address is alive. + auto WriteToMemory(Address a, const Value* v, int line_num) -> void; + // Print the value at the given address to the stream `out`. + auto PrintAddress(Address a, std::ostream& out) -> void; + // Signal an error if the address is no longer alive. + void CheckAlive(Address address, int line_num); + // Put the given value on the heap and mark it as alive. + auto AllocateValue(const Value* v) -> Address; + // Marks the object at this address, and all of its sub-objects, as dead. + auto KillObject(Address address) -> void; + // Print all the values on the heap to the stream `out`. + auto PrintHeap(std::ostream& out) -> void; + + private: + std::vector heap; }; extern State* state; @@ -70,7 +90,6 @@ extern State* state; auto PrintFrame(Frame* frame, std::ostream& out) -> void; void PrintStack(Stack ls, std::ostream& out); void PrintEnv(Env values); -auto AllocateValue(const Value* v) -> Address; auto CopyVal(const Value* val, int line_num) -> const Value*; auto ToInteger(const Value* v) -> int; diff --git a/executable_semantics/interpreter/typecheck.cpp b/executable_semantics/interpreter/typecheck.cpp index 0f3793a5bf18..0350a9827d57 100644 --- a/executable_semantics/interpreter/typecheck.cpp +++ b/executable_semantics/interpreter/typecheck.cpp @@ -58,8 +58,9 @@ auto ReifyType(const Value* t, int line_num) -> const Expression* { case ValKind::TupleV: { auto args = new std::vector>(); for (auto& field : *t->u.tuple.elts) { - args->push_back( - {field.first, ReifyType(state->heap[field.second], line_num)}); + args->push_back({field.first, ReifyType(state->ReadFromMemory( + field.second, line_num), + line_num)}); } return MakeTuple(0, args); } @@ -137,7 +138,7 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, std::cerr << std::endl; exit(-1); } - auto field_t = state->heap[*field_address]; + auto field_t = state->ReadFromMemory(*field_address, e->line_num); auto new_e = MakeIndex(e->line_num, res.exp, MakeInt(e->line_num, i)); return TCResult(new_e, field_t, res.types); } @@ -164,13 +165,13 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, << arg->first << std::endl; exit(-1); } - arg_expected = state->heap[*expected_field]; + arg_expected = state->ReadFromMemory(*expected_field, e->line_num); } auto arg_res = TypeCheckExp(arg->second, new_types, values, arg_expected, context); new_types = arg_res.types; new_args->push_back(std::make_pair(arg->first, arg_res.exp)); - arg_types->push_back({arg->first, AllocateValue(arg_res.type)}); + arg_types->push_back({arg->first, state->AllocateValue(arg_res.type)}); } auto tuple_e = MakeTuple(e->line_num, new_args); auto tuple_t = MakeTupleVal(arg_types); @@ -207,7 +208,9 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values, if (*e->u.get_field.field == field.first) { auto new_e = MakeGetField(e->line_num, res.exp, *e->u.get_field.field); - return TCResult(new_e, state->heap[field.second], res.types); + return TCResult(new_e, + state->ReadFromMemory(field.second, e->line_num), + res.types); } } std::cerr << e->line_num << ": compilation error, struct " @@ -677,11 +680,11 @@ auto FunctionDeclaration::TopLevel(TypeCheckContext& tops) const -> void { auto StructDeclaration::TopLevel(TypeCheckContext& tops) const -> void { auto st = TypeOfStructDef(&definition, tops.types, tops.values); - Address a = AllocateValue(st); + Address a = state->AllocateValue(st); tops.values.Set(Name(), a); // Is this obsolete? auto field_types = new std::vector>(); for (const auto& [field_name, field_value] : *st->u.struct_type.fields) { - field_types->push_back({field_name, AllocateValue(field_value)}); + field_types->push_back({field_name, state->AllocateValue(field_value)}); } auto fun_ty = MakeFunTypeVal(MakeTupleVal(field_types), st); tops.types.Set(Name(), fun_ty); @@ -694,7 +697,7 @@ auto ChoiceDeclaration::TopLevel(TypeCheckContext& tops) const -> void { alts->push_back(std::make_pair(a.first, t)); } auto ct = MakeChoiceTypeVal(name, alts); - Address a = AllocateValue(ct); + Address a = state->AllocateValue(ct); tops.values.Set(Name(), a); // Is this obsolete? tops.types.Set(Name(), ct); } diff --git a/executable_semantics/interpreter/value.cpp b/executable_semantics/interpreter/value.cpp index b1b2f062fab6..15df99a8fe97 100644 --- a/executable_semantics/interpreter/value.cpp +++ b/executable_semantics/interpreter/value.cpp @@ -213,7 +213,14 @@ auto MakeChoiceTypeVal(std::string name, return v; } -void PrintValue(const Value* val, std::ostream& out) { +auto State::PrintAddress(Address a, std::ostream& out) -> void { + if (!this->alive[a]) { + out << "!!"; + } + PrintValue(this->heap[a], out); +} + +auto PrintValue(const Value* val, std::ostream& out) -> void { switch (val->tag) { case ValKind::AltConsV: { out << *val->u.alt_cons.choice_name << "." << *val->u.alt_cons.alt_name; @@ -227,7 +234,7 @@ void PrintValue(const Value* val, std::ostream& out) { case ValKind::AltV: { out << "alt " << *val->u.alt.choice_name << "." << *val->u.alt.alt_name << " "; - PrintValue(state->heap[val->u.alt.argument], out); + state->PrintAddress(val->u.alt.argument, out); break; } case ValKind::StructV: { @@ -246,7 +253,7 @@ void PrintValue(const Value* val, std::ostream& out) { } out << elt.first << " = "; - PrintValue(state->heap[elt.second], out); + state->PrintAddress(elt.second, out); out << "@" << elt.second; } out << ")"; @@ -336,8 +343,8 @@ auto TypeEqual(const Value* t1, const Value* t2) -> bool { if (t2_field == std::nullopt) { return false; } - if (!TypeEqual(state->heap[(*t1->u.tuple.elts)[i].second], - state->heap[*t2_field])) { + if (!TypeEqual(state->ReadFromMemory((*t1->u.tuple.elts)[i].second, 0), + state->ReadFromMemory(*t2_field, 0))) { return false; } } @@ -363,8 +370,6 @@ auto ValueEqual(const Value* v1, const Value* v2, int line_num) -> bool { case ValKind::BoolV: return v1->u.boolean == v2->u.boolean; case ValKind::PtrV: - CheckAlive(v1->u.ptr, line_num); - CheckAlive(v2->u.ptr, line_num); return v1->u.ptr == v2->u.ptr; case ValKind::FunV: return v1->u.fun.body == v2->u.fun.body; @@ -402,13 +407,4 @@ auto ToInteger(const Value* v) -> int { } } -void CheckAlive(Address address, int line_num) { - if (!state->alive[address]) { - std::cerr << line_num << ": undefined behavior: access to dead value "; - PrintValue(state->heap[address], std::cerr); - std::cerr << std::endl; - exit(-1); - } -} - } // namespace Carbon diff --git a/executable_semantics/interpreter/value.h b/executable_semantics/interpreter/value.h index 8f3b8014f095..13f4c3432bab 100644 --- a/executable_semantics/interpreter/value.h +++ b/executable_semantics/interpreter/value.h @@ -160,7 +160,6 @@ auto TypeEqual(const Value* t1, const Value* t2) -> bool; auto ValueEqual(const Value* v1, const Value* v2, int line_num) -> bool; auto ToInteger(const Value* v) -> int; -void CheckAlive(Address a, int line_num); } // namespace Carbon diff --git a/executable_semantics/testdata/experimental_continuation9.6c b/executable_semantics/testdata/experimental_continuation9.6c new file mode 100644 index 000000000000..3a6639cd10c1 --- /dev/null +++ b/executable_semantics/testdata/experimental_continuation9.6c @@ -0,0 +1,21 @@ +// 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 + +// Test that the read from x triggers an error because x is dead. +// This test also demonstrates how by-reference free-variable capture +// is dangerous and can happen inside continuations. + +fn capture() -> __Continuation { + var Int: x = 1; + __continuation k { + var Int: y = x; + } + return k; +} + +fn main() -> Int { + var __Continuation: k = capture(); + __run k; // error, lifetime of x is over + return 0; +} diff --git a/executable_semantics/testdata/experimental_continuation9.golden b/executable_semantics/testdata/experimental_continuation9.golden new file mode 100644 index 000000000000..91c187caa5b0 --- /dev/null +++ b/executable_semantics/testdata/experimental_continuation9.golden @@ -0,0 +1,2 @@ +12: undefined behavior: access to dead value 1 +EXIT CODE: 255