summaryrefslogtreecommitdiff
path: root/mi
diff options
context:
space:
mode:
authorOri Bernstein <ori@eigenstate.org>2017-07-26 22:02:02 -0700
committerOri Bernstein <ori@eigenstate.org>2017-07-29 10:16:35 -0700
commit404ed1ddb3faa4fec7ee4fd8fddc5bbdec284bde (patch)
tree49c9399882a1eac7320e02482d809ac096deca21 /mi
parent9a2f63d47a96bf522e6c5d39e215b2e6a9e600c7 (diff)
downloadmc-404ed1ddb3faa4fec7ee4fd8fddc5bbdec284bde.tar.gz
Automatic variables
Hello, I wrote this patch to start a conversation about resource management in Myrddin. ~ Introduction ~~~~~~~~~~~~~~ The patch attached provides a hopefully life-improving and surely non-invasive mechanism to handle resources that have block-limited lifetimes: automatic variables. This resource-management scheme can be found in multiple languages today. For example, C++ calls destructors automatically at the end of the scope of a variable; similarly, Rust automatically calls user-defined code when a variable's scope ends; finally, also related is Go's defer statement which ensures that resource-releasing code is called at the end of a function. ~ Description ~~~~~~~~~~~~~ The idea is that every "binder" of the language (var/const/ fn args/match) offers the possibility to mark the variables it binds as "automatic" using the 'auto' keyword. An automatic variable must be of a type that implements the new builtin 'disposable' trait below. When the scope of a variable 'v' marked as automatic ends, '__dispose__(v)' is called. That's it. trait disposable @a = __dispose__ : (val : @a -> void) ;; ~ Example Programs ~~~~~~~~~~~~~~~~~~ The language modification is shown in action in the program below. use std impl disposable int = __dispose__ = {x std.put("__dispose__({})\n", x) } ;; const g = {auto x -> x++ - 1 } const main = { var auto i, auto j = 42 for i = 1; i < 6; i=i+1 var auto z : int = 2*i if i == 3 std.put("z = {} -- cont\n", z) continue ;; std.put("z = {}\n", z) if i/2 == 2 var auto inner : int = 1234 break ;; ;; i = g(321) } The output of the previous test program is: 1: z = 2 2: __dispose__(2) 3: z = 4 4: __dispose__(4) 5: z = 6 -- cont 6: __dispose__(6) 7: z = 8 8: __dispose__(1234) 9: __dispose__(8) 10: __dispose__(322) 11: __dispose__(42) 12: __dispose__(320) Some important remarks: * Unlike Go, __dispose__ is called as soon as the scope of a variable ends, not at the end of the function. In particular, the variable 'z' in the example is always disposed of before starting the next iteration. (An iteration ends the loop body block.) * __dispose__ is called in reverse order of declaration This allows variables to depend on resources of variables already in scope. * Regardless of how a block is exited (fallthrough, break, continue, return), variables of the blocks left are disposed of in reverse order and exactly once. * As line 10 of the output shows, the __dispose__ calls happen "after" the return statement of the function. (It happens after the post-increment, so x's value when it is disposed of is 322.) The following example shows that, using an ad hoc type, it is possible to execute arbitrary code at the end of a scope. type defer = (-> void) impl disposable defer = __dispose__ = {f: defer; f()} ;; const foobar = {... const auto _dummy = ({ std.put("Bye!\n") }: defer) ... } ~ Discussion ~~~~~~~~~~~~ Multiple alternatives exist for resource management, and hopefully this mail starts an interesting debate. According to me, here are the pros and cons of the current proposal: - PROS - * Opt-in * Backward compatible * Simple * The spirit of C's original auto * It has an implementation - CONS - * No safety guarantees/compiler checks whatsoever * Syntactic pollution: 'auto' keyword, 'disposable' trait Finally, note that the current patch does not implement auto support for variables bound in match statements. This will come in a followup patch if there is sufficient interest. Also, the patch does not provide proper support (or proper errors) for gotos and labels.
Diffstat (limited to 'mi')
-rw-r--r--mi/flatten.c179
1 files changed, 128 insertions, 51 deletions
diff --git a/mi/flatten.c b/mi/flatten.c
index 005ed06..77216c7 100644
--- a/mi/flatten.c
+++ b/mi/flatten.c
@@ -15,6 +15,14 @@
#include "mi.h"
#include "../config.h"
+typedef struct Flattenctx Flattenctx;
+typedef struct Loop Loop;
+
+struct Loop {
+ Node *lcnt;
+ Node *lbrk;
+ Stab *body;
+};
/* takes a list of nodes, and reduces it (and it's subnodes) to a list
* following these constraints:
@@ -22,27 +30,23 @@
* - Nodes with side effects are root node
* - All nodes operate on machine-primitive types and tuple
*/
-typedef struct Flattenctx Flattenctx;
struct Flattenctx {
int isglobl;
- /* return handling */
Node **stmts;
size_t nstmts;
/* return handling */
- int hasenv;
- int isbigret;
+ Node *tret;
/* pre/postinc handling */
Node **incqueue;
size_t nqueue;
/* break/continue handling */
- Node **loopstep;
- size_t nloopstep;
- Node **loopexit;
- size_t nloopexit;
+ Loop loop;
+ unsigned inloop;
+ Stab *curst;
/* location handling */
Htab *globls;
@@ -199,6 +203,50 @@ visit(Flattenctx *s, Node *n)
return r;
}
+static Node *
+traitfn(Srcloc loc, Trait *tr, char *fn, Type *ty)
+{
+ Node *proto, *dcl, *var;
+ char *name;
+ size_t i;
+
+ for (i = 0; i < tr->nproto; i++) {
+ name = declname(tr->proto[i]);
+ if (!strcmp(fn, name)) {
+ proto = tr->proto[i];
+ dcl = htget(proto->decl.impls, ty);
+ var = mkexpr(loc, Ovar, dcl->decl.name, NULL);
+ var->expr.type = dcl->decl.type;
+ var->expr.did = dcl->decl.did;
+ return var;
+ }
+ }
+ return NULL;
+}
+
+static void
+dispose(Flattenctx *s, Stab *st)
+{
+ Node *d, *call, *func, *val;
+ Trait *tr;
+ Type *ty;
+ size_t i;
+
+ tr = traittab[Tcdisp];
+ /* dispose in reverse order of declaration */
+ for (i = st->nautodcl; i-- > 0;) {
+ d = st->autodcl[i];
+ ty = decltype(d);
+ val = mkexpr(Zloc, Ovar, d->decl.name, NULL);
+ val->expr.type = ty;
+ val->expr.did = d->decl.did;
+ func = traitfn(Zloc, tr, "__dispose__", ty);
+ call = mkexpr(Zloc, Ocall, func, val, NULL);
+ call->expr.type = mktype(Zloc, Tyvoid);
+ flatten(s, call);
+ }
+}
+
static void
flattencond(Flattenctx *s, Node *n, Node *ltrue, Node *lfalse)
{
@@ -368,6 +416,26 @@ assign(Flattenctx *s, Node *lhs, Node *rhs)
return r;
}
+/* returns 1 when the exit jump needs to be emitted */
+static int
+exitscope(Flattenctx *s, Stab *stop, Srcloc loc, int x)
+{
+ Stab *st;
+
+ for (st = s->curst;; st = st->super) {
+ if (st->exit[x]) {
+ jmp(s, st->exit[x]);
+ return 0;
+ }
+ st->exit[x] = genlbl(loc);
+ flatten(s, st->exit[x]);
+ dispose(s, st);
+ if ((!stop && st->isfunc) || st == stop) {
+ return 1;
+ }
+ }
+}
+
static Node *
rval(Flattenctx *s, Node *n)
{
@@ -499,22 +567,28 @@ rval(Flattenctx *s, Node *n)
append(s, s->incqueue[i]);
lfree(&s->incqueue, &s->nqueue);
}
- append(s, mkexpr(n->loc, Oret, t, NULL));
+ if (!s->tret)
+ s->tret = temp(s, v);
+ flatten(s, asn(lval(s, s->tret), t));
+ if (exitscope(s, NULL, Zloc, Xret))
+ append(s, mkexpr(n->loc, Oret, s->tret, NULL));
break;
case Oasn:
r = assign(s, args[0], args[1]);
break;
case Obreak:
r = NULL;
- if (s->nloopexit == 0)
+ if (s->inloop == 0)
fatal(n, "trying to break when not in loop");
- jmp(s, s->loopexit[s->nloopexit - 1]);
+ if (exitscope(s, s->loop.body, n->loc, Xbrk))
+ jmp(s, s->loop.lbrk);
break;
case Ocontinue:
r = NULL;
- if (s->nloopstep == 0)
+ if (s->inloop == 0)
fatal(n, "trying to continue when not in loop");
- jmp(s, s->loopstep[s->nloopstep - 1]);
+ if (exitscope(s, s->loop.body, n->loc, Xcnt))
+ jmp(s, s->loop.lcnt);
break;
case Oeq: case One:
r = compare(s, n, 1);
@@ -569,14 +643,20 @@ lval(Flattenctx *s, Node *n)
}
static void
-flattenblk(Flattenctx *fc, Node *n)
+flattenblk(Flattenctx *s, Node *n)
{
+ Stab *st;
size_t i;
+ st = s->curst;
+ s->curst = n->block.scope;
for (i = 0; i < n->block.nstmts; i++) {
n->block.stmts[i] = fold(n->block.stmts[i], 0);
- flatten(fc, n->block.stmts[i]);
+ flatten(s, n->block.stmts[i]);
}
+ assert(s->curst == n->block.scope);
+ dispose(s, s->curst);
+ s->curst = st;
}
/* init; while cond; body;;
@@ -593,6 +673,8 @@ flattenblk(Flattenctx *fc, Node *n)
static void
flattenloop(Flattenctx *s, Node *n)
{
+ Stab *b;
+ Loop l;
Node *lbody;
Node *lend;
Node *ldec;
@@ -606,8 +688,14 @@ flattenloop(Flattenctx *s, Node *n)
lstep = genlbl(n->loc);
lend = genlbl(n->loc);
- lappend(&s->loopstep, &s->nloopstep, lstep);
- lappend(&s->loopexit, &s->nloopexit, lend);
+
+ b = s->curst;
+ s->curst = n->loopstmt.scope;
+ l = s->loop;
+ s->loop.lcnt = lstep;
+ s->loop.lbrk = lend;
+ s->loop.body = n->loopstmt.scope;
+ s->inloop++;
flatten(s, n->loopstmt.init); /* init */
jmp(s, lcond); /* goto test */
@@ -627,8 +715,9 @@ flattenloop(Flattenctx *s, Node *n)
append(s, s->incqueue[i]);
lfree(&s->incqueue, &s->nqueue);
- s->nloopstep--;
- s->nloopexit--;
+ s->inloop--;
+ s->loop = l;
+ s->curst = b;
}
/* if foo; bar; else baz;;
@@ -719,6 +808,7 @@ flattenloopmatch(Flattenctx *s, Node *pat, Node *val, Node *ltrue, Node *lfalse)
static void
flattenidxiter(Flattenctx *s, Node *n)
{
+ Loop l;
Node *lbody, *lstep, *lcond, *lmatch, *lend;
Node *idx, *len, *dcl, *seq, *val, *done;
Node *zero;
@@ -730,8 +820,11 @@ flattenidxiter(Flattenctx *s, Node *n)
lmatch = genlbl(n->loc);
lend = genlbl(n->loc);
- lappend(&s->loopstep, &s->nloopstep, lstep);
- lappend(&s->loopexit, &s->nloopexit, lend);
+ s->inloop++;
+ l = s->loop;
+ s->loop.lcnt = lstep;
+ s->loop.lbrk = lend;
+ s->loop.body = n->iterstmt.body->block.scope;
/* FIXME: pass this in from main() */
idxtype = mktype(n->loc, Tyuint64);
@@ -766,29 +859,8 @@ flattenidxiter(Flattenctx *s, Node *n)
jmp(s, lbody);
flatten(s, lend);
- s->nloopstep--;
- s->nloopexit--;
-}
-
-static Node *
-itertraitfn(Srcloc loc, Trait *tr, char *fn, Type *ty)
-{
- Node *proto, *dcl, *var;
- char *name;
- size_t i;
-
- for (i = 0; i < tr->nproto; i++) {
- name = declname(tr->proto[i]);
- if (!strcmp(fn, name)) {
- proto = tr->proto[i];
- dcl = htget(proto->decl.impls, ty);
- var = mkexpr(loc, Ovar, dcl->decl.name, NULL);
- var->expr.type = dcl->decl.type;
- var->expr.did = dcl->decl.did;
- return var;
- }
- }
- return NULL;
+ s->inloop--;
+ s->loop = l;
}
/* for pat in seq
@@ -812,6 +884,7 @@ itertraitfn(Srcloc loc, Trait *tr, char *fn, Type *ty)
static void
flattentraititer(Flattenctx *s, Node *n)
{
+ Loop l;
Node *lbody, *lclean, *lstep, *lmatch, *lend;
Node *done, *val, *iter, *valptr, *iterptr;
Node *func, *call;
@@ -831,8 +904,12 @@ flattentraititer(Flattenctx *s, Node *n)
lstep = genlbl(n->loc);
lmatch = genlbl(n->loc);
lend = genlbl(n->loc);
- lappend(&s->loopstep, &s->nloopstep, lstep);
- lappend(&s->loopexit, &s->nloopexit, lend);
+
+ s->inloop++;
+ l = s->loop;
+ s->loop.lcnt = lstep;
+ s->loop.lbrk = lend;
+ s->loop.body = n->iterstmt.body->block.scope;
append(s, asn(iter, n->iterstmt.seq));
jmp(s, lstep);
@@ -842,14 +919,14 @@ flattentraititer(Flattenctx *s, Node *n)
flatten(s, lclean);
/* call iterator cleanup */
- func = itertraitfn(n->loc, tr, "__iterfin__", exprtype(iter));
+ func = traitfn(n->loc, tr, "__iterfin__", exprtype(iter));
call = mkexpr(n->loc, Ocall, func, iterptr, valptr, NULL);
call->expr.type = mktype(n->loc, Tyvoid);
append(s, call);
flatten(s, lstep);
/* call iterator step */
- func = itertraitfn(n->loc, tr, "__iternext__", exprtype(iter));
+ func = traitfn(n->loc, tr, "__iternext__", exprtype(iter));
call = mkexpr(n->loc, Ocall, func, iterptr, valptr, NULL);
done = gentemp(n->loc, mktype(n->loc, Tybool), NULL);
call->expr.type = exprtype(done);
@@ -862,8 +939,8 @@ flattentraititer(Flattenctx *s, Node *n)
jmp(s, lbody);
flatten(s, lend);
- s->nloopstep--;
- s->nloopexit--;
+ s->inloop--;
+ s->loop = l;
}
static void
@@ -954,6 +1031,7 @@ flatteninit(Node *dcl)
lit = dcl->decl.init->expr.args[0];
fn = lit->lit.fnval;
body = fn->func.body;
+ fc.curst = fn->func.scope;
flatten(&fc, fn->func.body);
blk = mkblock(fn->loc, body->block.scope);
blk->block.stmts = fc.stmts;
@@ -1013,4 +1091,3 @@ isconstfn(Node *n)
return 0;
return 1;
}
-