Factor Heap class out of State (#491)

Additional miscellaneous cleanup:

- Use the term "deallocate" instead of "kill" in function names, for symmetry with "allocate".
- Drop an unnecessary memory allocation in `AllocateValue`.
- Implement `PrintHeap` in terms of `PrintAddress`, so that dead values are flagged with `!!`.
This commit is contained in:
Geoff Romer
2021-04-23 13:25:51 -07:00
committed by GitHub
parent a13cecccc6
commit e022ff106f
4 changed files with 127 additions and 109 deletions
@@ -31,31 +31,33 @@ void HandleValue();
// Auxiliary Functions
//
auto State::AllocateValue(const Value* v) -> Address {
auto Heap::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 = heap.size();
heap.push_back(new Value(*v));
this->alive.push_back(true);
assert(v != nullptr);
Address a = values_.size();
values_.push_back(v);
alive_.push_back(true);
return a;
}
auto State::ReadFromMemory(Address a, int line_num) -> const Value* {
auto Heap::Read(Address a, int line_num) -> const Value* {
this->CheckAlive(a, line_num);
return heap[a];
return values_[a];
}
auto State::WriteToMemory(Address a, const Value* v, int line_num) -> void {
auto Heap::Write(Address a, const Value* v, int line_num) -> void {
assert(v != nullptr);
this->CheckAlive(a, line_num);
heap[a] = v;
values_[a] = v;
}
void State::CheckAlive(Address address, int line_num) {
if (!this->alive[address]) {
void Heap::CheckAlive(Address address, int line_num) {
if (!alive_[address]) {
std::cerr << line_num << ": undefined behavior: access to dead value ";
PrintValue(heap[address], std::cerr);
PrintValue(values_[address], std::cerr);
std::cerr << std::endl;
exit(-1);
}
@@ -67,16 +69,16 @@ auto CopyVal(const Value* val, int line_num) -> const Value* {
auto elts = new std::vector<std::pair<std::string, Address>>();
for (auto& i : *val->u.tuple.elts) {
const Value* elt =
CopyVal(state->ReadFromMemory(i.second, line_num), line_num);
Address new_address = state->AllocateValue(elt);
CopyVal(state->heap.Read(i.second, line_num), line_num);
Address new_address = state->heap.AllocateValue(elt);
elts->push_back(make_pair(i.first, new_address));
}
return MakeTupleVal(elts);
}
case ValKind::AltV: {
const Value* arg = CopyVal(
state->ReadFromMemory(val->u.alt.argument, line_num), line_num);
Address argument_address = state->AllocateValue(arg);
const Value* arg =
CopyVal(state->heap.Read(val->u.alt.argument, line_num), line_num);
Address argument_address = state->heap.AllocateValue(arg);
return MakeAltVal(*val->u.alt.alt_name, *val->u.alt.choice_name,
argument_address);
}
@@ -122,18 +124,17 @@ auto CopyVal(const Value* val, int line_num) -> const Value* {
}
}
// Marks all of the sub-objects of this value as dead.
void KillSubObjects(const Value* val) {
void Heap::DeallocateSubObjects(const Value* val) {
switch (val->tag) {
case ValKind::AltV:
state->KillObject(val->u.alt.argument);
Deallocate(val->u.alt.argument);
break;
case ValKind::StructV:
KillSubObjects(val->u.struct_val.inits);
DeallocateSubObjects(val->u.struct_val.inits);
break;
case ValKind::TupleV:
for (auto& elt : *val->u.tuple.elts) {
state->KillObject(elt.second);
Deallocate(elt.second);
}
break;
default:
@@ -141,12 +142,13 @@ void KillSubObjects(const Value* val) {
}
}
void State::KillObject(Address address) {
if (this->alive[address]) {
this->alive[address] = false;
KillSubObjects(heap[address]);
void Heap::Deallocate(Address address) {
if (alive_[address]) {
alive_[address] = false;
DeallocateSubObjects(values_[address]);
} else {
std::cerr << "runtime error, killing an already dead value" << std::endl;
std::cerr << "runtime error, deallocating an already dead value"
<< std::endl;
exit(-1);
}
}
@@ -154,7 +156,7 @@ void State::KillObject(Address address) {
void PrintEnv(Env values, std::ostream& out) {
for (const auto& [name, address] : values) {
out << name << ": ";
state->PrintAddress(address, out);
state->heap.PrintAddress(address, out);
out << ", ";
}
}
@@ -180,17 +182,20 @@ void PrintStack(Stack<Frame*> ls, std::ostream& out) {
}
}
void State::PrintHeap(std::ostream& out) {
for (auto& iter : heap) {
if (iter) {
PrintValue(iter, out);
} else {
out << "_";
}
void Heap::PrintHeap(std::ostream& out) {
for (Address i = 0; i < values_.size(); ++i) {
PrintAddress(i, out);
out << ", ";
}
}
auto Heap::PrintAddress(Address a, std::ostream& out) -> void {
if (!alive_[a]) {
out << "!!";
}
PrintValue(values_[a], out);
}
auto CurrentEnv(State* state) -> Env {
Frame* frame = state->stack.Top();
return frame->scopes.Top()->values;
@@ -201,7 +206,7 @@ void PrintState(std::ostream& out) {
out << "stack: ";
PrintStack(state->stack, out);
out << std::endl << "heap: ";
state->PrintHeap(out);
state->heap.PrintHeap(out);
if (!state->stack.IsEmpty() && !state->stack.Top()->scopes.IsEmpty()) {
out << std::endl << "values: ";
PrintEnv(CurrentEnv(state), out);
@@ -300,7 +305,7 @@ auto ChoiceDeclaration::InitGlobals(Env& globals) const -> void {
alts->push_back(make_pair(kv.first, t));
}
auto ct = MakeChoiceTypeVal(name, alts);
auto a = state->AllocateValue(ct);
auto a = state->heap.AllocateValue(ct);
globals.Set(name, a);
}
@@ -318,7 +323,7 @@ auto StructDeclaration::InitGlobals(Env& globals) const -> void {
}
}
auto st = MakeStructTypeVal(*definition.name, fields, methods);
auto a = state->AllocateValue(st);
auto a = state->heap.AllocateValue(st);
globals.Set(*definition.name, a);
}
@@ -326,7 +331,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 = state->AllocateValue(f);
Address a = state->heap.AllocateValue(f);
globals.Set(definition->name, a);
}
@@ -334,7 +339,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 = state->AllocateValue(v);
Address a = state->heap.AllocateValue(v);
globals.Set(name, a);
}
@@ -373,7 +378,7 @@ void CallFunction(int line_num, std::vector<const Value*> operas,
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,
state->AllocateValue(arg));
state->heap.AllocateValue(arg));
Frame* frame = state->stack.Top();
frame->todo.Push(MakeValAct(av));
break;
@@ -386,20 +391,20 @@ void CallFunction(int line_num, std::vector<const Value*> operas,
}
}
void KillScope(int line_num, Scope* scope) {
void DeallocateScope(int line_num, Scope* scope) {
for (const auto& l : scope->locals) {
std::optional<Address> a = scope->values.Get(l);
if (!a) {
std::cerr << "internal error in KillScope" << std::endl;
std::cerr << "internal error in DeallocateScope" << std::endl;
exit(-1);
}
state->KillObject(*a);
state->heap.Deallocate(*a);
}
}
void KillLocals(int line_num, Frame* frame) {
void DeallocateLocals(int line_num, Frame* frame) {
for (auto scope : frame->scopes) {
KillScope(line_num, scope);
DeallocateScope(line_num, scope);
}
}
@@ -409,7 +414,7 @@ void CreateTuple(Frame* frame, Action* act, const Expression* /*exp*/) {
auto elts = new std::vector<std::pair<std::string, Address>>();
auto f = act->u.exp->u.tuple.fields->begin();
for (auto i = act->results.begin(); i != act->results.end(); ++i, ++f) {
Address a = state->AllocateValue(*i); // copy?
Address a = state->heap.AllocateValue(*i); // copy?
elts->push_back(make_pair(f->first, a));
}
const Value* tv = MakeTupleVal(elts);
@@ -427,7 +432,7 @@ auto PatternMatch(const Value* p, const Value* v, Env values,
-> std::optional<Env> {
switch (p->tag) {
case ValKind::VarPatV: {
Address a = state->AllocateValue(CopyVal(v, line_num));
Address a = state->heap.AllocateValue(CopyVal(v, line_num));
vars->push_back(*p->u.var_pat.name);
values.Set(*p->u.var_pat.name, a);
return values;
@@ -449,8 +454,8 @@ auto PatternMatch(const Value* p, const Value* v, Env values,
exit(-1);
}
std::optional<Env> matches = PatternMatch(
state->ReadFromMemory(elt.second, line_num),
state->ReadFromMemory(*a, line_num), values, vars, line_num);
state->heap.Read(elt.second, line_num),
state->heap.Read(*a, line_num), values, vars, line_num);
if (!matches) {
return std::nullopt;
}
@@ -473,8 +478,8 @@ auto PatternMatch(const Value* p, const Value* v, Env values,
return std::nullopt;
}
std::optional<Env> matches =
PatternMatch(state->ReadFromMemory(p->u.alt.argument, line_num),
state->ReadFromMemory(v->u.alt.argument, line_num),
PatternMatch(state->heap.Read(p->u.alt.argument, line_num),
state->heap.Read(v->u.alt.argument, line_num),
values, vars, line_num);
if (!matches) {
return std::nullopt;
@@ -515,8 +520,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->WriteToMemory(ValToPtr(pat, line_num), CopyVal(val, line_num),
line_num);
state->heap.Write(ValToPtr(pat, line_num), CopyVal(val, line_num),
line_num);
break;
case ValKind::TupleV: {
switch (val->tag) {
@@ -534,8 +539,8 @@ void PatternAssignment(const Value* pat, const Value* val, int line_num) {
std::cerr << std::endl;
exit(-1);
}
PatternAssignment(state->ReadFromMemory(elt.second, line_num),
state->ReadFromMemory(*a, line_num), line_num);
PatternAssignment(state->heap.Read(elt.second, line_num),
state->heap.Read(*a, line_num), line_num);
}
break;
}
@@ -557,9 +562,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->ReadFromMemory(pat->u.alt.argument, line_num),
state->ReadFromMemory(val->u.alt.argument, line_num), line_num);
PatternAssignment(state->heap.Read(pat->u.alt.argument, line_num),
state->heap.Read(val->u.alt.argument, line_num),
line_num);
break;
}
default:
@@ -699,7 +704,7 @@ void StepExp() {
<< *(exp->u.variable.name) << "`" << std::endl;
exit(-1);
}
const Value* pointee = state->ReadFromMemory(*pointer, exp->line_num);
const Value* pointee = state->heap.Read(*pointer, exp->line_num);
frame->todo.Pop(1);
frame->todo.Push(MakeValAct(pointee));
break;
@@ -832,7 +837,7 @@ void StepStmt() {
frame->todo.Pop(1);
while (!frame->todo.IsEmpty() && !IsWhileAct(frame->todo.Top())) {
if (IsBlockAct(frame->todo.Top())) {
KillScope(stmt->line_num, frame->scopes.Top());
DeallocateScope(stmt->line_num, frame->scopes.Top());
frame->scopes.Pop(1);
}
frame->todo.Pop(1);
@@ -845,7 +850,7 @@ void StepStmt() {
frame->todo.Pop(1);
while (!frame->todo.IsEmpty() && !IsWhileAct(frame->todo.Top())) {
if (IsBlockAct(frame->todo.Top())) {
KillScope(stmt->line_num, frame->scopes.Top());
DeallocateScope(stmt->line_num, frame->scopes.Top());
frame->scopes.Pop(1);
}
frame->todo.Pop(1);
@@ -863,7 +868,7 @@ void StepStmt() {
}
} else {
Scope* scope = frame->scopes.Top();
KillScope(stmt->line_num, scope);
DeallocateScope(stmt->line_num, scope);
frame->scopes.Pop(1);
frame->todo.Pop(1);
}
@@ -919,7 +924,7 @@ void StepStmt() {
todo.Push(MakeStmtAct(stmt->u.continuation.body));
Frame* continuation_frame = new Frame("__continuation", scopes, todo);
Address continuation_address =
state->AllocateValue(MakeContinuation({continuation_frame}));
state->heap.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
@@ -942,14 +947,14 @@ void StepStmt() {
paused.push_back(state->stack.Pop());
} while (!paused.back()->IsContinuation());
// Update the continuation with the paused stack.
state->WriteToMemory(paused.back()->continuation,
MakeContinuation(paused), stmt->line_num);
state->heap.Write(paused.back()->continuation, MakeContinuation(paused),
stmt->line_num);
break;
}
}
auto GetMember(Address a, const std::string& f, int line_num) -> Address {
const Value* v = state->ReadFromMemory(a, line_num);
const Value* v = state->heap.Read(a, line_num);
switch (v->tag) {
case ValKind::StructV: {
auto a = FindTupleField(f, v->u.struct_val.inits);
@@ -979,7 +984,7 @@ auto GetMember(Address a, const std::string& f, int line_num) -> Address {
exit(-1);
}
auto ac = MakeAltCons(f, *v->u.choice_type.name);
return state->AllocateValue(ac);
return state->heap.AllocateValue(ac);
}
default:
std::cerr << "field access not allowed for value ";
@@ -1032,13 +1037,13 @@ void HandleValue() {
}
switch (act->tag) {
case ActionKind::DeleteTmpAction: {
state->KillObject(act->u.delete_tmp);
state->heap.Deallocate(act->u.delete_tmp);
frame->todo.Pop(2);
frame->todo.Push(val_act);
break;
}
case ActionKind::ExpToLValAction: {
Address a = state->AllocateValue(act->results[0]);
Address a = state->heap.AllocateValue(act->results[0]);
auto del = MakeDeleteAct(a);
frame->todo.Pop(2);
InsertDelete(del, frame->todo);
@@ -1145,7 +1150,7 @@ void HandleValue() {
exit(-1);
}
frame->todo.Pop(2);
const Value* element = state->ReadFromMemory(*a, exp->line_num);
const Value* element = state->heap.Read(*a, exp->line_num);
frame->todo.Push(MakeValAct(element));
break;
}
@@ -1164,7 +1169,7 @@ void HandleValue() {
// -> { { v_f :: C, E, F} : S, H}
auto a = GetMember(ValToPtr(act->results[0], exp->line_num),
*exp->u.get_field.field, exp->line_num);
const Value* element = state->ReadFromMemory(a, exp->line_num);
const Value* element = state->heap.Read(a, exp->line_num);
frame->todo.Pop(2);
frame->todo.Push(MakeValAct(element));
break;
@@ -1378,7 +1383,7 @@ void HandleValue() {
// { {v :: return [] :: C, E, F} :: {C', E', F'} :: S, H}
// -> { {v :: C', E', F'} :: S, H}
const Value* ret_val = CopyVal(val_act->u.val, stmt->line_num);
KillLocals(stmt->line_num, frame);
DeallocateLocals(stmt->line_num, frame);
state->stack.Pop(1);
frame = state->stack.Top();
frame->todo.Push(MakeValAct(ret_val));
+31 -12
View File
@@ -59,30 +59,49 @@ struct Frame {
continuation(UINT_MAX) {}
};
// TODO(geoffromer): Make this a class, with all members private
struct State {
Stack<Frame*> stack;
std::vector<bool> alive;
// A Heap represents the abstract machine's dynamically allocated memory.
class Heap {
public:
// Constructs an empty Heap.
Heap() = default;
Heap(const Heap&) = delete;
Heap& operator=(const Heap&) = delete;
// 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*;
auto Read(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);
auto Write(Address a, const Value* v, int line_num) -> void;
// 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;
auto Deallocate(Address address) -> void;
// Print the value at the given address to the stream `out`.
auto PrintAddress(Address a, std::ostream& out) -> void;
// Print all the values on the heap to the stream `out`.
auto PrintHeap(std::ostream& out) -> void;
private:
std::vector<const Value*> heap;
// Signal an error if the address is no longer alive.
void CheckAlive(Address address, int line_num);
// Marks all sub-objects of this value as dead.
void DeallocateSubObjects(const Value* val);
std::vector<const Value*> values_;
std::vector<bool> alive_;
};
struct State {
Stack<Frame*> stack;
Heap heap;
};
extern State* state;
+13 -11
View File
@@ -59,9 +59,9 @@ auto ReifyType(const Value* t, int line_num) -> const Expression* {
case ValKind::TupleV: {
auto args = new std::vector<std::pair<std::string, const Expression*>>();
for (auto& field : *t->u.tuple.elts) {
args->push_back({field.first, ReifyType(state->ReadFromMemory(
field.second, line_num),
line_num)});
args->push_back(
{field.first,
ReifyType(state->heap.Read(field.second, line_num), line_num)});
}
return MakeTuple(0, args);
}
@@ -161,7 +161,7 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values,
std::cerr << std::endl;
exit(-1);
}
auto field_t = state->ReadFromMemory(*field_address, e->line_num);
auto field_t = state->heap.Read(*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);
}
@@ -200,14 +200,15 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values,
<< " but got " << arg->first << std::endl;
exit(-1);
}
arg_expected = state->ReadFromMemory(
(*expected->u.tuple.elts)[i].second, e->line_num);
arg_expected = state->heap.Read((*expected->u.tuple.elts)[i].second,
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, state->AllocateValue(arg_res.type)});
arg_types->push_back(
{arg->first, state->heap.AllocateValue(arg_res.type)});
}
auto tuple_e = MakeTuple(e->line_num, new_args);
auto tuple_t = MakeTupleVal(arg_types);
@@ -245,7 +246,7 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values,
auto new_e =
MakeGetField(e->line_num, res.exp, *e->u.get_field.field);
return TCResult(new_e,
state->ReadFromMemory(field.second, e->line_num),
state->heap.Read(field.second, e->line_num),
res.types);
}
}
@@ -715,11 +716,12 @@ auto FunctionDeclaration::TopLevel(TypeCheckContext& tops) const -> void {
auto StructDeclaration::TopLevel(TypeCheckContext& tops) const -> void {
auto st = TypeOfStructDef(&definition, tops.types, tops.values);
Address a = state->AllocateValue(st);
Address a = state->heap.AllocateValue(st);
tops.values.Set(Name(), a); // Is this obsolete?
auto field_types = new std::vector<std::pair<std::string, Address>>();
for (const auto& [field_name, field_value] : *st->u.struct_type.fields) {
field_types->push_back({field_name, state->AllocateValue(field_value)});
field_types->push_back(
{field_name, state->heap.AllocateValue(field_value)});
}
auto fun_ty = MakeFunTypeVal(MakeTupleVal(field_types), st);
tops.types.Set(Name(), fun_ty);
@@ -732,7 +734,7 @@ auto ChoiceDeclaration::TopLevel(TypeCheckContext& tops) const -> void {
alts->push_back(std::make_pair(a.first, t));
}
auto ct = MakeChoiceTypeVal(name, alts);
Address a = state->AllocateValue(ct);
Address a = state->heap.AllocateValue(ct);
tops.values.Set(Name(), a); // Is this obsolete?
tops.types.Set(Name(), ct);
}
+6 -14
View File
@@ -214,13 +214,6 @@ auto MakeChoiceTypeVal(std::string name,
return v;
}
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: {
@@ -235,7 +228,7 @@ auto PrintValue(const Value* val, std::ostream& out) -> void {
case ValKind::AltV: {
out << "alt " << *val->u.alt.choice_name << "." << *val->u.alt.alt_name
<< " ";
state->PrintAddress(val->u.alt.argument, out);
state->heap.PrintAddress(val->u.alt.argument, out);
break;
}
case ValKind::StructV: {
@@ -254,7 +247,7 @@ auto PrintValue(const Value* val, std::ostream& out) -> void {
}
out << elt.first << " = ";
state->PrintAddress(elt.second, out);
state->heap.PrintAddress(elt.second, out);
out << "@" << elt.second;
}
out << ")";
@@ -342,9 +335,8 @@ auto TypeEqual(const Value* t1, const Value* t2) -> bool {
if ((*t1->u.tuple.elts)[i].first != (*t2->u.tuple.elts)[i].first) {
return false;
}
if (!TypeEqual(
state->ReadFromMemory((*t1->u.tuple.elts)[i].second, 0),
state->ReadFromMemory((*t2->u.tuple.elts)[i].second, 0))) {
if (!TypeEqual(state->heap.Read((*t1->u.tuple.elts)[i].second, 0),
state->heap.Read((*t2->u.tuple.elts)[i].second, 0))) {
return false;
}
}
@@ -378,8 +370,8 @@ static auto FieldsValueEqual(VarAddresses* ts1, VarAddresses* ts2, int line_num)
if (iter == ts2->end()) {
return false;
}
if (!ValueEqual(state->ReadFromMemory(address, line_num),
state->ReadFromMemory(iter->second, line_num), line_num)) {
if (!ValueEqual(state->heap.Read(address, line_num),
state->heap.Read(iter->second, line_num), line_num)) {
return false;
}
}