From 1c3b2475ca2008b7a68e7fbd6281b59fa84a286c Mon Sep 17 00:00:00 2001 From: eunhhu Date: Mon, 19 Oct 2026 07:32:09 +0100 Subject: Targets together compiled straight when they can be: S_TOGETHER holds the targets with their values; when no value may read what a target before it changes (a target's name, a fn's call, a parameter or a binding, an index or a for's item of a changed list), each target takes its value in order with no lets, else the values go through lets first as before; layout.aup's boxes 1,525.9 instructions a node through lets, 1,492.8 straight (the hand-written 1,493.9); frictions reads a changed item through a let's index and a for's item (a compiler always straight fails it) --- compiler/compile.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- tests/check.py | 15 +++++++-- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/compiler/compile.c b/compiler/compile.c index f161182..982a500 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -108,7 +108,7 @@ typedef struct Expr { } Expr; typedef enum { S_ASSIGN, S_IF, S_REPLY, S_REQUIRE, S_INSERT, S_REMOVE, S_APPEND, S_ADD, S_SET, S_SEND, S_FOR, - S_LET, S_COUNT, S_BREAK, S_RETURN, S_ASSIGNS, S_LETS, S_MATCH, S_ARM, S_CALL } StmtKind; + S_LET, S_COUNT, S_BREAK, S_RETURN, S_ASSIGNS, S_LETS, S_MATCH, S_ARM, S_CALL, S_TOGETHER } StmtKind; typedef struct Stmt { StmtKind kind; int line, column; @@ -122,7 +122,8 @@ typedef struct Stmt { int library, key_line, key_column; char key[NAME]; /* S_ASSIGNS and S_LETS: a, b = x, y gives every name its value at once (the values first, in order): the names, and - * the values linked from value by next. */ + * the values linked from value by next. S_TOGETHER: a, xs[i].x = x, y, its targets (S_ASSIGN, S_SET) from then, each + * with its value, or value the one call that gives them all. */ char (*names)[NAME]; unsigned name_count; int refills; /* S_LET of xs = value's copy: xs's list + 1 (its value must be a list of its record), else 0 */ @@ -1293,7 +1294,19 @@ static Stmt *together(Compiler *c, Stmt *first) { for (unsigned i = 0; names && i < n; ++i) memcpy(s->names[i], targets[i]->name, NAME); s->kind = S_ASSIGNS; several_values(c, s); - return names ? s : through_lets(c, s, targets); + if (names) return s; + s->kind = S_TOGETHER; /* compiled straight, or through lets when a value may read what a target changes */ + unsigned count = 0; + for (const Expr *v = s->value; v; v = v->next) ++count; + for (unsigned i = 0; count == n && i < n; ++i) { + Expr *after = s->value->next; + s->value->next = NULL; + targets[i]->value = s->value; + s->value = after; + } + for (unsigned i = 0; i < n; ++i) targets[i]->next = i + 1 < n ? targets[i + 1] : NULL; + s->then = targets[0]; + return s; } static Stmt *statement(Compiler *c, unsigned depth) { @@ -4571,6 +4584,54 @@ static Stmt *emptied(Compiler *c, const char *name, int line, int column) { return loop; } +static bool listed(const int *lists, unsigned n, int list) { + for (unsigned i = 0; i < n; ++i) if (lists[i] == list) return true; + return false; +} + +/* Whether e may read what S_TOGETHER's targets change (names, of them; lists, those changed): a target's name, a fn's + * call (it may read anything), a parameter or a case's binding (their kinds are the instance's), or an index of a + * list changed (a let's, a for's item, a state's) through which a changed item is read. */ +static bool reads_changed(Compiler *c, const Expr *e, char (*names)[NAME], unsigned n, const int *lists, unsigned l) { + if (!e) return false; + if (e->kind == E_FN) return true; + for (unsigned i = 0; i < n; ++i) + if (((e->kind == E_NAME || e->kind == E_ITEM) && !strcmp(e->name, names[i])) || + (e->kind == E_FOLD && !strcmp(e->member, names[i]))) + return true; + if (e->kind == E_NAME || e->kind == E_ITEM) { + if (!c->module && (parameter(c, e->name) >= 0 || alias_named(c, e->name) >= 0)) return true; + if (c->module) return true; + int local = local_named(c, e->name), loop = local < 0 ? loop_named(c, e->name) : -1, + state = local < 0 && loop < 0 ? state_index(c, e->name) : -1; + if (local >= 0 && c->locals[local].kind.type == INDEX && listed(lists, l, c->locals[local].kind.owner)) return true; + if (loop >= 0 && listed(lists, l, c->loops[loop].list)) return true; + if (state >= 0 && c->state_types[state] == STATE_INDEX && listed(lists, l, cursor_owner(c, state))) return true; + } + for (unsigned i = 0; i < PARAMS; ++i) + for (const Expr *x = e->args[i]; x; x = x->next) + if (reads_changed(c, x, names, n, lists, l)) return true; + return false; +} + +/* S_TOGETHER's targets each given its value straight, in order: when no value reads what a target before it changes + * (then the values first, as written, is the same). */ +static bool straight(Compiler *c, const Stmt *s) { + if (s->value) return false; /* one call gives them all */ + char (*names)[NAME] = allocate(c, STACK * sizeof(*names)); + int lists[STACK]; + unsigned n = 0, l = 0; + for (const Stmt *t = s->then; t; t = t->next) { + if (!t->name[0]) return false; /* at.x: the list of its index is not named */ + memcpy(names[n++], t->name, NAME); + int list = local_named(c, t->name) < 0 ? state_index(c, t->name) : -1; + if (list >= 0 && c->state_types[list] == STATE_LIST) lists[l++] = list; + } + for (const Stmt *t = s->then; t; t = t->next) + if (reads_changed(c, t->value, names, n, lists, l)) return false; + return true; +} + /* xs = a list value (a fn's, a let's, another list's): a copy of it first (xs may be in it), then xs emptied, then * given the copy's items in order (added by key when xs is keyed). */ static Stmt *refilled(Compiler *c, const Stmt *s, int list) { @@ -4842,6 +4903,37 @@ static void statements(Compiler *c, const Stmt *s) { for (unsigned i = s->name_count; i-- > 0;) pop_named(c, local_named(c, s->names[i]), s->line, s->column); break; } + case S_TOGETHER: { + if (straight(c, s)) { + statements(c, s->then); + break; + } + Stmt *spread = allocate(c, sizeof(*spread)), *targets[STACK]; /* copies: a fn's body compiles for each instance */ + *spread = *s; + spread->kind = S_LETS, spread->then = spread->next = NULL; + spread->names = allocate(c, STACK * sizeof(*spread->names)); + Expr **link = &spread->value; + if (s->value) { /* one call gives them all */ + *link = node(c, s->value->kind, s->value->line, s->value->column); + **link = *s->value; + link = &(*link)->next; + } + unsigned n = 0; + for (const Stmt *t = s->then; t; t = t->next) { + targets[n] = allocate(c, sizeof(*targets[n])); + *targets[n] = *t; + targets[n++]->next = NULL; + if (t->value) { + *link = node(c, t->value->kind, t->value->line, t->value->column); + **link = *t->value; + link = &(*link)->next; + } + } + *link = NULL; + spread->name_count = n; + statements(c, through_lets(c, spread, targets)); + break; + } case S_ASSIGNS: { const Expr *v = s->value; int locals[STACK], states[STACK]; diff --git a/tests/check.py b/tests/check.py index 4a9daea..6d39d9e 100644 --- a/tests/check.py +++ b/tests/check.py @@ -675,6 +675,8 @@ state wide: i64 state per = 0 state hexed = 0 state bits: i64 +state n2 = 0 +state n3 = 0 input tick = every 250 ms input go = message { k: u32 } input edge = message { x: i64 } @@ -714,6 +716,11 @@ on go { ns.append(0) ns[0], ns[1] = 3, go.k b = b + ns[0] * 100 + ns[1] + let q = bs.first + bs[0].y, n2 = 5, q.y + for p in bs { + if p.y == 5 { bs[0].y, n3 = 6, p.y } + } let xs, ys, zs, us: list of number let ps, qs: list of P zs.append(1) @@ -1827,7 +1834,8 @@ def rows_at(aup, pure_run, work, events=200): def frictions(aup, pure_run, work, events=100): """140's frictions written away in aup alone, no core change: lets' lists declared together (let a, b: list of R), each its own and empty as it begins, in inner blocks too; targets together with fields and items among them - (xs[i].x, xs[i].y = a, b; a, xs[i] = ...), the values first, then each target left to right as Python's; a list + (xs[i].x, xs[i].y = a, b; a, xs[i] = ...), the values first, then each target left to right as Python's (straight + when no value may read what a target changes; an index or a for's item of a changed list is read first); a list given a list value whole (xs = f(x), xs = ys, xs = xs, xs = [] for a list state too, xs, j = f(x)), added by key when keyed; abs(x) of a number and an i64 (x once); a timer's period (tick.period); 0x... a number where a number goes (compared with one, in its arithmetic, given to one), an i64 elsewhere. Each event against Python's.""" @@ -1856,9 +1864,12 @@ def frictions(aup, pure_run, work, events=100): ns = [0, 0] ns[0], ns[1] = 3, k b = b + ns[0] * 100 + ns[1] + n2, bs[0]['y'] = bs[0]['y'], 5 # q is bs's first: its y read before the item changes + for p in bs: + if p['y'] == 5: n3, bs[0]['y'] = p['y'], 6 want = {'total': sum(range(k)) * (1 + 200 + 10000), 'n': n, 'bs': bs, 'a': a, 'b': b, 'i': i, 'kept': kept, 'keyed': keyed, 'j': k + 100, 'far': abs(k - 4.5), 'wide': abs(k - 4), 'per': 250, - 'hexed': (k == 2) + 10 * (k == 3) + min(k, 5) * 100 + 16, 'bits': (k | 0xf0) & 0xff} + 'hexed': (k == 2) + 10 * (k == 3) + min(k, 5) * 100 + 16, 'bits': (k | 0xf0) & 0xff, 'n2': n2, 'n3': n3} assert state['fault'] == 'none' and {key: state['states'][key] for key in want} == want, (k, state['states']) edges = ''.join(f"message edge {struct.pack('