mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
Fix the two known limits: adding map keys while looping over the map is a runtime error; temporaries held by an expression are freed when try / or-blocks leave early. err usable after or, inner err shadows outer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b872be3176
commit
f9838dfe03
9 changed files with 228 additions and 17 deletions
|
|
@ -596,10 +596,11 @@ int finch_try_write_file(const FStr *path, const FStr *text, FStr *err) {
|
|||
}
|
||||
|
||||
// ---------- maps ----------
|
||||
// map[K]V = { entries; len; used; cap; index; icap }
|
||||
// map[K]V = { entries; len; used; cap; index; icap; adds }
|
||||
// entries: `used` slots of es bytes, each { uint64 hash; K key; V value }, in insertion order.
|
||||
// A removed entry keeps its slot (hash 0) until the next resize packs the slots.
|
||||
// index: icap slots (a power of two, at least 2 * cap): -1 empty, -2 removed, else an entry number.
|
||||
// adds: how many keys were ever added; a `for` over the map stops with an error if it changes.
|
||||
// The compiler stores, copies and drops keys and values; the runtime only finds and places entries.
|
||||
// Keys are compared byte for byte (numbers, chars, bools) or as text (kstr).
|
||||
|
||||
|
|
@ -608,6 +609,7 @@ typedef struct {
|
|||
int64_t len, used, cap;
|
||||
int64_t *index;
|
||||
int64_t icap;
|
||||
int64_t adds;
|
||||
} FMap;
|
||||
|
||||
static uint64_t map_hash(const void *key, int64_t ks, int kstr) {
|
||||
|
|
@ -699,6 +701,7 @@ int64_t finch_map_slot(FMap *m, const void *key, int64_t es, int64_t ks, int32_t
|
|||
while (m->index[s] >= 0) s = (s + 1) & (m->icap - 1);
|
||||
m->index[s] = i;
|
||||
m->len++;
|
||||
m->adds++;
|
||||
return i;
|
||||
}
|
||||
|
||||
|
|
@ -736,6 +739,10 @@ void finch_map_clone_raw(FMap *out, const FMap *m, int64_t es) {
|
|||
memcpy(out->index, m->index, (size_t)m->icap * sizeof(int64_t));
|
||||
}
|
||||
|
||||
__attribute__((noreturn, cold)) void finch_map_changed(const char *file, int64_t line) {
|
||||
finch_panic(file, line, "a key was added to the map while looping over it (collect the new keys, and add them after the loop)");
|
||||
}
|
||||
|
||||
__attribute__((noreturn, cold)) void finch_map_missing(const char *file, int64_t line, const FStr *key) {
|
||||
char msg[200];
|
||||
snprintf(msg, sizeof msg, "the key %.*s is not in the map (check with .has(key), or use .get(key, default))",
|
||||
|
|
|
|||
|
|
@ -296,6 +296,21 @@ void Codegen::define(Fn &fn) {
|
|||
|
||||
void Codegen::pushScope() { scopes.emplace_back(); }
|
||||
|
||||
void Codegen::hold(const Value_ &v) {
|
||||
if (v.fresh && v.v && !isa<Constant>(v.v) && owning(v.type)) pending.push_back({v.v, v.type, false});
|
||||
}
|
||||
|
||||
void Codegen::holdAddr(Value *addr, const FType &t) {
|
||||
if (owning(t)) pending.push_back({addr, t, true});
|
||||
}
|
||||
|
||||
void Codegen::dropPending(size_t downTo) {
|
||||
for (size_t i = pending.size(); i-- > downTo;) {
|
||||
if (pending[i].isAddr) dropAt(pending[i].v, pending[i].type);
|
||||
else dropValue(pending[i].v, pending[i].type);
|
||||
}
|
||||
}
|
||||
|
||||
void Codegen::popScope() {
|
||||
if (!terminated()) emitScopeCleanups(scopes.size() - 1);
|
||||
scopes.pop_back();
|
||||
|
|
@ -352,7 +367,8 @@ Var *Codegen::lookup(const std::string &name) {
|
|||
|
||||
Var &Codegen::addVar(Pos p, const std::string &name, const FType &t, Value *init, bool owned, bool readonly,
|
||||
unsigned argNo) {
|
||||
if (lookup(name)) failAt(p.file, p.line, p.col, "a variable named '" + name + "' already exists here");
|
||||
// `err` of an inner `or` hides the outer one: f() or g() or err
|
||||
if (lookup(name) && name != "err") failAt(p.file, p.line, p.col, "a variable named '" + name + "' already exists here");
|
||||
ModuleScope &m = modules[curModule];
|
||||
if (m.fns.count(name)) failAt(p.file, p.line, p.col, "'" + name + "' is already the name of a function");
|
||||
if (m.imports.count(name)) failAt(p.file, p.line, p.col, "'" + name + "' is already the name of an imported module");
|
||||
|
|
@ -427,6 +443,7 @@ void Codegen::jump(bool isBreak, Pos p) {
|
|||
if (inDefer) failAt(p.file, p.line, p.col, std::string(word) + " can't be used inside defer");
|
||||
if (loops.empty()) failAt(p.file, p.line, p.col, std::string(word) + " can only be used inside a loop");
|
||||
Loop l = loops.back();
|
||||
dropPending(l.pendingDepth);
|
||||
emitCleanups(l.scopeDepth);
|
||||
b.CreateBr(isBreak ? l.breakTo : l.continueTo);
|
||||
}
|
||||
|
|
@ -466,6 +483,8 @@ void Codegen::assign(const AssignStmt &s) {
|
|||
if (Var *var = lookup(root)) { want = var->type; known = true; }
|
||||
}
|
||||
v = known ? exprWant(*s.value, want) : expr(*s.value);
|
||||
Held held(*this);
|
||||
hold(v); // while the target is computed (a[try f()] = ...)
|
||||
Place dst = place(*s.target, true);
|
||||
v = coerce(v, dst.type, s.value->pos, what);
|
||||
Value *nv = own(v);
|
||||
|
|
@ -481,6 +500,8 @@ void Codegen::assign(const AssignStmt &s) {
|
|||
BinOp op = s.op == '+' ? BinOp::Add : s.op == '-' ? BinOp::Sub : s.op == '*' ? BinOp::Mul
|
||||
: s.op == '/' ? BinOp::Div : BinOp::Mod;
|
||||
Value_ rhs = expr(*s.value);
|
||||
Held held(*this);
|
||||
hold(rhs);
|
||||
Place dst = place(*s.target, true);
|
||||
Value_ cur{b.CreateLoad(ty(dst.type), dst.addr), dst.type};
|
||||
Value_ v = coerce(arith(op, cur, rhs, s.pos), dst.type, s.pos, what);
|
||||
|
|
@ -538,7 +559,7 @@ void Codegen::whileStmt(const WhileStmt &s) {
|
|||
else b.CreateCondBr(cond, body, end);
|
||||
|
||||
b.SetInsertPoint(body);
|
||||
loops.push_back({condBB, end, scopes.size()});
|
||||
loops.push_back({condBB, end, scopes.size(), pending.size()});
|
||||
block(*s.body);
|
||||
loops.pop_back();
|
||||
if (!terminated()) b.CreateBr(condBB);
|
||||
|
|
@ -564,7 +585,7 @@ void Codegen::forStmt(const ForStmt &s) {
|
|||
b.CreateCondBr(b.CreateICmpSLT(b.CreateLoad(b.getInt64Ty(), i), to), body, end);
|
||||
|
||||
b.SetInsertPoint(body);
|
||||
loops.push_back({step, end, scopes.size()});
|
||||
loops.push_back({step, end, scopes.size(), pending.size()});
|
||||
block(*s.body);
|
||||
loops.pop_back();
|
||||
if (!terminated()) b.CreateBr(step);
|
||||
|
|
@ -600,10 +621,20 @@ void Codegen::forEachStmt(const ForEachStmt &s) {
|
|||
bool copyKV = live && mutates(rootVar(*s.list), *s.body);
|
||||
Value *i = slot(FType::I64, "$i");
|
||||
b.CreateStore(b.getInt64(0), i);
|
||||
// adding keys inside the loop could reorder the entries: that stops the program (removing is fine)
|
||||
Value *adds = live ? b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(MT, addr, 6)) : nullptr;
|
||||
BasicBlock *condBB = newBlock("map.cond"), *check = newBlock("map.check"), *body = newBlock("map.body"),
|
||||
*step = newBlock("map.step"), *end = newBlock("map.end");
|
||||
b.CreateBr(condBB);
|
||||
b.SetInsertPoint(condBB);
|
||||
if (adds && mutates(rootVar(*s.list), *s.body)) {
|
||||
BasicBlock *changed = newBlock("map.changed"), *same = newBlock("map.same");
|
||||
b.CreateCondBr(b.CreateICmpNE(b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(MT, addr, 6)), adds), changed, same);
|
||||
b.SetInsertPoint(changed);
|
||||
b.CreateCall(rt("finch_map_changed"), {fileName(s.pos), b.getInt64(s.pos.line)});
|
||||
b.CreateUnreachable();
|
||||
b.SetInsertPoint(same);
|
||||
}
|
||||
Value *iv = b.CreateLoad(b.getInt64Ty(), i);
|
||||
b.CreateCondBr(b.CreateICmpSLT(iv, b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(MT, addr, 2))), check, end);
|
||||
b.SetInsertPoint(check);
|
||||
|
|
@ -611,7 +642,7 @@ void Codegen::forEachStmt(const ForEachStmt &s) {
|
|||
Value *hash = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ET, entry, 0));
|
||||
b.CreateCondBr(b.CreateICmpEQ(hash, b.getInt64(0)), step, body); // a removed entry
|
||||
b.SetInsertPoint(body);
|
||||
loops.push_back({step, end, scopes.size()});
|
||||
loops.push_back({step, end, scopes.size(), pending.size()});
|
||||
pushScope();
|
||||
std::string why = " can't be changed (to change the map, use m[key] = ...)";
|
||||
Value *kv = b.CreateLoad(ty(kt), b.CreateStructGEP(ET, entry, 1));
|
||||
|
|
@ -662,7 +693,7 @@ void Codegen::forEachStmt(const ForEachStmt &s) {
|
|||
elemAddr = b.CreateInBoundsGEP(ty(et), data, iv);
|
||||
}
|
||||
Value *elem = b.CreateLoad(ty(et), elemAddr);
|
||||
loops.push_back({step, end, scopes.size()});
|
||||
loops.push_back({step, end, scopes.size(), pending.size()});
|
||||
pushScope();
|
||||
std::string elemVar = s.var;
|
||||
Pos elemPos = s.varPos;
|
||||
|
|
@ -718,6 +749,7 @@ void Codegen::returnStmt(const ReturnStmt &s) {
|
|||
if (std::string(bd.name) == "error") note(c.pos, 5, "```finch\n" + std::string(bd.signature) + "\n```\n" + bd.doc);
|
||||
checkArgs(c.args, c.argNames, 1, c.pos, "error");
|
||||
Value *msg = own(coerce(expr(*c.args[0]), FType::Str, c.args[0]->pos, "the error message"));
|
||||
dropPending(0);
|
||||
emitCleanups(0);
|
||||
retError(msg);
|
||||
return;
|
||||
|
|
@ -725,6 +757,7 @@ void Codegen::returnStmt(const ReturnStmt &s) {
|
|||
if (curFn->ret.kind == FType::Void) {
|
||||
if (s.value)
|
||||
failAt(s.value->pos.file, s.value->pos.line, s.value->pos.col, "'" + d.name + "' doesn't return a value (it has no '-> type')");
|
||||
dropPending(0);
|
||||
emitCleanups(0);
|
||||
retValue(nullptr);
|
||||
return;
|
||||
|
|
@ -740,6 +773,7 @@ void Codegen::returnStmt(const ReturnStmt &s) {
|
|||
if (it->second.owned && it->second.type == curFn->ret) {
|
||||
Value *v = b.CreateLoad(ty(it->second.type), it->second.slot);
|
||||
it->second.moved = true;
|
||||
dropPending(0);
|
||||
emitCleanups(0);
|
||||
scopes[idx].vars[name].moved = false;
|
||||
retValue(v);
|
||||
|
|
@ -750,6 +784,7 @@ void Codegen::returnStmt(const ReturnStmt &s) {
|
|||
}
|
||||
Value_ v = coerce(exprWant(*s.value, curFn->ret), curFn->ret, s.value->pos, "the returned value");
|
||||
Value *out = own(v);
|
||||
dropPending(0);
|
||||
emitCleanups(0);
|
||||
retValue(out);
|
||||
}
|
||||
|
|
@ -889,6 +924,7 @@ Value_ Codegen::tryExpr(const TryExpr &t) {
|
|||
BasicBlock *bad = newBlock("try.failed"), *ok = newBlock("try.ok");
|
||||
b.CreateCondBr(failed, bad, ok);
|
||||
b.SetInsertPoint(bad);
|
||||
dropPending(0); // what the expressions around this `try` were holding
|
||||
emitCleanups(0);
|
||||
retError(msg);
|
||||
b.SetInsertPoint(ok);
|
||||
|
|
@ -927,8 +963,10 @@ Value_ Codegen::orElse(const OrElseExpr &e, bool discard) {
|
|||
Pos fp = e.fallback->pos;
|
||||
if (v.type.kind == FType::Void)
|
||||
failAt(fp.file, fp.line, fp.col, "this call gives back no value, so there is nothing to replace; handle the failure with or { ... }");
|
||||
dropValue(msg, FType::Str);
|
||||
pushScope(); // the fallback can use the message too: f(x) or err
|
||||
addVar(fp, "err", FType::Str, msg, true);
|
||||
Value *fv = own(coerce(exprWant(*e.fallback, v.type), v.type, fp, "the value after 'or'"));
|
||||
popScope();
|
||||
BasicBlock *badEnd = b.GetInsertBlock();
|
||||
BasicBlock *join = newBlock("or.end");
|
||||
b.CreateBr(join);
|
||||
|
|
@ -1126,6 +1164,8 @@ LRef Codegen::index(const IndexExpr &e, bool forWrite) {
|
|||
Pos p = e.pos;
|
||||
LRef obj = ref(*e.obj, forWrite);
|
||||
FType t = obj.isPlace ? obj.pl.type : obj.val.type;
|
||||
Held held(*this);
|
||||
if (!obj.isPlace) hold(obj.val); // while the index is computed
|
||||
if (t.kind == FType::Map) return mapIndex(e, obj, t, forWrite);
|
||||
Value *i = coerce(expr(*e.index), FType::I64, e.index->pos, "an index").v;
|
||||
|
||||
|
|
@ -1180,6 +1220,7 @@ Value_ Codegen::arrayLit(const ArrayLitExpr &a, const FType *want) {
|
|||
if (allNum && anyFloat) et = FType::F64;
|
||||
}
|
||||
std::vector<Value *> vals;
|
||||
Held held(*this);
|
||||
for (size_t k = 0; k < a.elems.size(); k++) {
|
||||
Value_ v = et.kind == FType::Void ? exprWant(*a.elems[k], et) : exprWant(*a.elems[k], et);
|
||||
if (et.kind == FType::Void) {
|
||||
|
|
@ -1189,6 +1230,7 @@ Value_ Codegen::arrayLit(const ArrayLitExpr &a, const FType *want) {
|
|||
}
|
||||
v = coerce(v, et, a.elems[k]->pos, "every element of this array");
|
||||
vals.push_back(own(v));
|
||||
hold({vals.back(), et, false, true});
|
||||
}
|
||||
FType at = FType::arrayOf(et);
|
||||
Value *n = b.getInt64(vals.size());
|
||||
|
|
@ -1404,11 +1446,17 @@ Value_ Codegen::binary(const BinaryExpr &e) {
|
|||
case BinOp::Greater:
|
||||
case BinOp::GreaterEq: {
|
||||
Value_ l = expr(*e.lhs);
|
||||
return compare(e.op, l, expr(*e.rhs), e.pos);
|
||||
Held held(*this);
|
||||
hold(l);
|
||||
Value_ r = expr(*e.rhs);
|
||||
return compare(e.op, l, r, e.pos);
|
||||
}
|
||||
default: {
|
||||
Value_ l = expr(*e.lhs);
|
||||
return arith(e.op, l, expr(*e.rhs), e.pos);
|
||||
Held held(*this);
|
||||
hold(l);
|
||||
Value_ r = expr(*e.rhs);
|
||||
return arith(e.op, l, r, e.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ Function *Codegen::rt(const std::string &name) {
|
|||
{"finch_map_free", {V, {P}}},
|
||||
{"finch_map_clone_raw", {V, {P, P, I}}},
|
||||
{"finch_map_missing", {V, {P, I, P}}},
|
||||
{"finch_map_changed", {V, {P, I}}},
|
||||
{"finch_buf_add", {V, {P, P, I}}},
|
||||
{"finch_buf_int", {V, {P, I}}},
|
||||
{"finch_buf_uint", {V, {P, I}}},
|
||||
|
|
@ -125,7 +126,7 @@ Function *Codegen::rt(const std::string &name) {
|
|||
}
|
||||
Function *f = Function::Create(FunctionType::get(it->second.ret, it->second.params, false), Function::ExternalLinkage,
|
||||
name, mod.get());
|
||||
if (name == "finch_panic" || name == "finch_panic_index" || name == "finch_map_missing") {
|
||||
if (name == "finch_panic" || name == "finch_panic_index" || name == "finch_map_missing" || name == "finch_map_changed") {
|
||||
f->addFnAttr(Attribute::NoReturn);
|
||||
f->addFnAttr(Attribute::Cold);
|
||||
}
|
||||
|
|
@ -309,6 +310,8 @@ Value_ Codegen::call(const CallExpr &c) {
|
|||
if (n == "write_file") {
|
||||
checkArgs(c.args, c.argNames, 2, p, n);
|
||||
Value_ path = coerce(expr(*c.args[0]), FType::Str, c.args[0]->pos, "the file name");
|
||||
Held held(*this);
|
||||
hold(path);
|
||||
Value_ text = coerce(expr(*c.args[1]), FType::Str, c.args[1]->pos, "the text");
|
||||
if (canFail) {
|
||||
Value *err = tmp(zero(FType::Str));
|
||||
|
|
@ -360,6 +363,7 @@ Value_ Codegen::callFinch(const Fn &fn, const std::vector<ExprPtr> &args, const
|
|||
checkArgs(args, names, fn.params.size(), p, shown);
|
||||
std::vector<Value *> vals;
|
||||
std::vector<Value_> fresh;
|
||||
Held held(*this);
|
||||
if (self) vals.push_back(self);
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
Value_ v = coerce(exprWant(*args[i], fn.params[i]), fn.params[i], args[i]->pos, "argument '" + d.params[i].name + "'");
|
||||
|
|
@ -368,6 +372,7 @@ Value_ Codegen::callFinch(const Fn &fn, const std::vector<ExprPtr> &args, const
|
|||
v = {copyValue(v.v, v.type), v.type, false, true};
|
||||
vals.push_back(v.v); // owning values are lent: the function never drops them
|
||||
if (v.fresh) fresh.push_back(v);
|
||||
hold(v);
|
||||
}
|
||||
setLoc(p);
|
||||
Value *r = b.CreateCall(fn.llvm, vals);
|
||||
|
|
@ -412,6 +417,7 @@ Value_ Codegen::construct(StructInfo *s, const std::vector<ExprPtr> &args, const
|
|||
}
|
||||
|
||||
Value *v = zero(t);
|
||||
Held held(*this);
|
||||
for (size_t k = 0; k < fields.size(); k++) {
|
||||
const StructInfo::F &f = *fields[k];
|
||||
Value_ fv;
|
||||
|
|
@ -432,7 +438,9 @@ Value_ Codegen::construct(StructInfo *s, const std::vector<ExprPtr> &args, const
|
|||
} else {
|
||||
continue; // zero
|
||||
}
|
||||
v = b.CreateInsertValue(v, own(fv), f.llvmIndex);
|
||||
Value *fo = own(fv);
|
||||
hold({fo, f.type, false, true});
|
||||
v = b.CreateInsertValue(v, fo, f.llvmIndex);
|
||||
}
|
||||
return {v, t, false, owning(t)};
|
||||
}
|
||||
|
|
@ -441,7 +449,9 @@ Value_ Codegen::callC(const CFunc &f, const std::vector<ExprPtr> &args, Pos p) {
|
|||
if (!f.unsupported.empty())
|
||||
failAt(p.file, p.line, p.col, "the C function '" + f.name + "' can't be used from Finch yet: it " + f.unsupported);
|
||||
std::vector<Value_> vals;
|
||||
Held held(*this);
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
if (!vals.empty()) hold(vals.back());
|
||||
if (i < f.params.size()) {
|
||||
FType pt = resolve(f.params[i], p);
|
||||
vals.push_back(coerce(exprWant(*args[i], pt), pt, args[i]->pos, "argument " + std::to_string(i + 1) + " of " + f.name));
|
||||
|
|
@ -592,8 +602,10 @@ Value_ Codegen::method(const MethodExpr &m) {
|
|||
std::string root = rootVar(*m.obj);
|
||||
if (Var *v = root.empty() ? nullptr : lookup(root); v && v->readonly) failAt(p.file, p.line, p.col, v->readonlyWhy);
|
||||
}
|
||||
Held held(*this);
|
||||
if (t.kind == FType::Map) {
|
||||
Value_ recv = r.isPlace ? Value_{r.pl.addr, t} : Value_{tmp(r.val.v), t, false, r.val.fresh};
|
||||
if (!r.isPlace && r.val.fresh) holdAddr(recv.v, t);
|
||||
Value_ out = mapMethod(m, &recv);
|
||||
if (!r.isPlace && r.val.fresh) dropAt(recv.v, t);
|
||||
return out;
|
||||
|
|
@ -611,6 +623,7 @@ Value_ Codegen::method(const MethodExpr &m) {
|
|||
if (!r.isPlace) {
|
||||
if (changing.count(m.name)) failAt(p.file, p.line, p.col, m.name + "() would change a temporary array; store it in a variable first");
|
||||
recv = {tmp(r.val.v), t, false, r.val.fresh};
|
||||
if (r.val.fresh) holdAddr(recv.v, t);
|
||||
}
|
||||
Value_ out = arrayMethod(m, &recv);
|
||||
if (!r.isPlace && r.val.fresh) dropAt(recv.v, t);
|
||||
|
|
@ -619,8 +632,11 @@ Value_ Codegen::method(const MethodExpr &m) {
|
|||
if (t.kind == FType::Str) {
|
||||
Value_ sv = r.isPlace ? Value_{b.CreateLoad(ty(t), r.pl.addr), t} : r.val;
|
||||
Value *addr = tmp(sv.v); // the runtime takes strings by address
|
||||
if (!r.isPlace) hold(sv);
|
||||
auto argStr = [&](size_t i, const char *what) {
|
||||
return coerce(expr(*m.args[i]), FType::Str, m.args[i]->pos, what);
|
||||
Value_ a = coerce(expr(*m.args[i]), FType::Str, m.args[i]->pos, what);
|
||||
hold(a);
|
||||
return a;
|
||||
};
|
||||
auto argInt = [&](size_t i, const char *what) {
|
||||
return coerce(expr(*m.args[i]), FType::I64, m.args[i]->pos, what).v;
|
||||
|
|
@ -714,6 +730,8 @@ Value_ Codegen::structMethod(const MethodExpr &m, LRef &r, const FType &t) {
|
|||
self = tmp(r.val.v); // a temporary struct: Point(1, 2).length()
|
||||
temp = true;
|
||||
}
|
||||
Held held(*this);
|
||||
if (temp && r.val.fresh) holdAddr(self, st);
|
||||
Value_ out = callFinch(*fn, m.args, m.argNames, p, st.name + "." + m.name, &m, self, root);
|
||||
if (temp && r.val.fresh && owning(st)) dropAt(self, st);
|
||||
return out;
|
||||
|
|
|
|||
|
|
@ -88,6 +88,15 @@ struct Fn {
|
|||
struct Loop {
|
||||
llvm::BasicBlock *continueTo, *breakTo;
|
||||
size_t scopeDepth; // scopes opened inside the loop are cleaned up by break/continue
|
||||
size_t pendingDepth; // and so are temporaries held by expressions around it (see Codegen::pending)
|
||||
};
|
||||
|
||||
// A fresh value an expression is holding while it computes another part (f(a + b, try g())): if that
|
||||
// part leaves early (try, or a return/break/continue in an `or { }` block), it must be dropped.
|
||||
struct Pending {
|
||||
llvm::Value *v;
|
||||
FType type;
|
||||
bool isAddr; // v is the value's address
|
||||
};
|
||||
|
||||
struct Place { // something that can be assigned to
|
||||
|
|
@ -136,6 +145,7 @@ public:
|
|||
std::vector<std::unique_ptr<StructInfo>> structStore;
|
||||
std::vector<Scope> scopes;
|
||||
std::vector<Loop> loops;
|
||||
std::vector<Pending> pending;
|
||||
const Fn *curFn = nullptr;
|
||||
std::string curModule;
|
||||
int varOrder = 0;
|
||||
|
|
@ -183,6 +193,15 @@ public:
|
|||
bool terminated();
|
||||
llvm::BasicBlock *newBlock(const char *name);
|
||||
|
||||
void hold(const Value_ &v); // see Pending
|
||||
void holdAddr(llvm::Value *addr, const FType &t);
|
||||
void dropPending(size_t downTo); // emit drops for pending[downTo..]
|
||||
struct Held { // forgets what was held when the expression is done
|
||||
Codegen &cg;
|
||||
size_t mark;
|
||||
explicit Held(Codegen &c) : cg(c), mark(c.pending.size()) {}
|
||||
~Held() { cg.pending.erase(cg.pending.begin() + mark, cg.pending.end()); }
|
||||
};
|
||||
void pushScope();
|
||||
void popScope(); // emits the scope's cleanups if reachable
|
||||
void emitCleanups(size_t downTo); // run cleanups of scopes [downTo, end) without popping
|
||||
|
|
|
|||
|
|
@ -138,9 +138,13 @@ Value_ Codegen::mapLit(const MapLitExpr &e, const FType *want) {
|
|||
}
|
||||
Value *map = tmp(zero(t));
|
||||
StructType *ET = mapEntryTy(t);
|
||||
Held held(*this);
|
||||
holdAddr(map, t); // the entries so far, if a later one leaves early
|
||||
for (size_t i = 0; i < e.keys.size(); i++) {
|
||||
Held entry(*this);
|
||||
Value_ k = i == 0 && first ? k0 : exprWant(*e.keys[i], *t.key);
|
||||
k = coerce(k, *t.key, e.keys[i]->pos, "every key of this map");
|
||||
hold(k);
|
||||
Value_ v = i == 0 && first ? v0 : exprWant(*e.values[i], *t.elem);
|
||||
Value *vv = own(coerce(v, *t.elem, e.values[i]->pos, "every value of this map"));
|
||||
Value *valueAddr = b.CreateStructGEP(ET, mapSlot(map, t, k, p, false), 2);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ llvm::Type *Codegen::ty(const FType &t) {
|
|||
case FType::Array: return StructType::get(ctx, {b.getPtrTy(), b.getInt64Ty(), b.getInt64Ty()});
|
||||
case FType::Map: {
|
||||
llvm::Type *P = b.getPtrTy(), *I = b.getInt64Ty();
|
||||
return StructType::get(ctx, {P, I, I, I, P, I}); // entries, len, used, cap, index, icap
|
||||
return StructType::get(ctx, {P, I, I, I, P, I, I}); // entries, len, used, cap, index, icap, adds
|
||||
}
|
||||
case FType::Ptr:
|
||||
case FType::Null: return b.getPtrTy();
|
||||
|
|
@ -546,11 +546,11 @@ DIType *Codegen::diType(const FType &t) {
|
|||
case FType::Map: { // shown by its parts; the entries are behind `entries`
|
||||
DIType *i64 = diType(FType::I64), *pt = di->createPointerType(nullptr, 64);
|
||||
DIFile *f = diFiles[0];
|
||||
const char *names[] = {"entries", "len", "used", "cap", "index", "icap"};
|
||||
const char *names[] = {"entries", "len", "used", "cap", "index", "icap", "adds"};
|
||||
std::vector<Metadata *> els;
|
||||
for (unsigned k = 0; k < 6; k++)
|
||||
for (unsigned k = 0; k < 7; k++)
|
||||
els.push_back(di->createMemberType(diUnit, names[k], f, 0, 64, 64, 64 * k, DINode::FlagZero, k == 0 || k == 4 ? pt : i64));
|
||||
d = di->createStructType(diUnit, t.show(), f, 0, 384, 64, DINode::FlagZero, nullptr, di->getOrCreateArray(els));
|
||||
d = di->createStructType(diUnit, t.show(), f, 0, 448, 64, DINode::FlagZero, nullptr, di->getOrCreateArray(els));
|
||||
break;
|
||||
}
|
||||
case FType::Struct: {
|
||||
|
|
|
|||
7
tests/fail/map_add_while_looping.fch
Normal file
7
tests/fail/map_add_while_looping.fch
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// expect: a key was added to the map while looping over it
|
||||
fn main() {
|
||||
m := ["a": 1]
|
||||
for k in m {
|
||||
m[k + "!"] = 2
|
||||
}
|
||||
}
|
||||
96
tests/run/early_exit.fch
Normal file
96
tests/run/early_exit.fch
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// Leaving an expression half way (try, or { break / continue / return }) frees what it was holding.
|
||||
// MEMCHECK=1 tests/run.sh checks this with valgrind.
|
||||
|
||||
struct Pair {
|
||||
str a
|
||||
str b
|
||||
}
|
||||
|
||||
fn check(int n) -> str! {
|
||||
if n > 0 {
|
||||
return error("failed at " + str(n))
|
||||
}
|
||||
return "ok"
|
||||
}
|
||||
|
||||
fn join3(str a, str b, str c) -> str {
|
||||
return a + b + c
|
||||
}
|
||||
|
||||
fn Pair.both() -> str {
|
||||
return self.a + self.b
|
||||
}
|
||||
|
||||
fn args(int n) -> str! {
|
||||
s := "x".repeat(3)
|
||||
return join3(s + "1", try check(n), s + "2")
|
||||
}
|
||||
|
||||
fn array(int n) -> []str! {
|
||||
s := "y".repeat(2)
|
||||
return [s + "a", s + "b", try check(n)]
|
||||
}
|
||||
|
||||
fn pair(int n) -> Pair! {
|
||||
s := "z".repeat(2)
|
||||
return Pair(s + "a", try check(n))
|
||||
}
|
||||
|
||||
fn concat(int n) -> str! {
|
||||
s := "w".repeat(2)
|
||||
return s + "1" + (try check(n)) + "2"
|
||||
}
|
||||
|
||||
fn method_on_temp(int n) -> str! {
|
||||
s := "v".repeat(2)
|
||||
return (s + "!").replace("v", try check(n))
|
||||
}
|
||||
|
||||
fn struct_method(int n) -> str! {
|
||||
s := "u".repeat(2)
|
||||
t := Pair(s, s + "?").both() + (try check(n))
|
||||
return t
|
||||
}
|
||||
|
||||
fn map_literal(int n) -> map[str]str! {
|
||||
s := "k".repeat(2)
|
||||
return [s: s + "1", s + "x": try check(n)]
|
||||
}
|
||||
|
||||
fn index_temp(int n) -> str! {
|
||||
s := "i".repeat(2)
|
||||
return [s + "0", s + "1"][int(try check(n)) or 0]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
for n in 0..2 {
|
||||
print(args(n) or err, "|", array(n) or ["-"], "|", pair(n) or Pair("-", "-"))
|
||||
print(concat(n) or err, "|", method_on_temp(n) or err, "|", struct_method(n) or err)
|
||||
print(map_literal(n) or ["-": "-"])
|
||||
}
|
||||
print(index_temp(1) or "index: failed")
|
||||
|
||||
// break / continue out of an or { } block in the middle of a call
|
||||
s := "q".repeat(2)
|
||||
for i in 0..4 {
|
||||
t := join3(s + "a", check(i % 2) or { continue }, s + "b")
|
||||
print(i, t)
|
||||
}
|
||||
for i in 0..4 {
|
||||
t := join3(s + "a", check(i) or { break }, s + "b")
|
||||
print("before break:", i, t)
|
||||
}
|
||||
|
||||
// removing keys while looping over a map is fine
|
||||
m := ["a": 1, "b": 2, "c": 3, "d": 4]
|
||||
for k, v in m {
|
||||
if v % 2 == 0 {
|
||||
m.remove(k)
|
||||
}
|
||||
}
|
||||
print(m)
|
||||
for k in m {
|
||||
m[k] = m[k] * 10 // changing values is fine too
|
||||
}
|
||||
print(m)
|
||||
}
|
||||
12
tests/run/early_exit.out
Normal file
12
tests/run/early_exit.out
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
xxx1okxxx2 | ["yya", "yyb", "ok"] | Pair(a: "zza", b: "ok")
|
||||
ww1ok2 | okok! | uuuu?ok
|
||||
{"kk": "kk1", "kkx": "ok"}
|
||||
failed at 1 | ["-"] | Pair(a: "-", b: "-")
|
||||
failed at 1 | failed at 1 | failed at 1
|
||||
{"-": "-"}
|
||||
index: failed
|
||||
0 qqaokqqb
|
||||
2 qqaokqqb
|
||||
before break: 0 qqaokqqb
|
||||
{"a": 1, "c": 3}
|
||||
{"a": 10, "c": 30}
|
||||
Loading…
Reference in a new issue