aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/044-specs/2026-10-19-inline-block.patch
blob: 83dfe3741463e6c4e6a234d8d51d126c2b3473cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
diff --git a/compiler/compile.c b/compiler/compile.c
index 0fcaa1b..47d525e 100644
--- a/compiler/compile.c
+++ b/compiler/compile.c
@@ -182,6 +182,8 @@ typedef struct {
     bool typed;  /* -> its results' types */
     bool active; /* being inlined: a call back into it is recursion */
     bool called; /* run as a function (a block, or a cycle of calls through it) */
+    bool inlined; /* a block inlined where it is called (its only return last, no let's list, no cycle: inline_block) */
+    bool loops;   /* its block holds a for or a while: inlined only where the stack is empty (Pure opens no loop above values) */
 } Fn;
 
 typedef struct { uint8_t op; uint32_t arg; union { double value; int64_t integer; }; } Op;
@@ -274,6 +276,13 @@ typedef struct {
      * fields; variant: a variant's case; alias: a match's name for a case's value (read, never assigned). */
     struct { char name[NAME]; unsigned slot; Kind kind; int list; bool counter, variant, alias; } locals[LOCALS];
     unsigned local_count, local_slots, held_bytes, held_texts; /* held: the bytes' and texts' lets' slots */
+    /* An inlined block's names begin at these floors (its parameters and lets alone); its slots, one set for each fn and
+     * kinds of arguments in a handler (or a function), used again at each call: where they start. */
+    unsigned local_floor, loop_floor, alias_floor;
+    struct { int fn; Kind kinds[PARAMS]; unsigned slots, bytes, texts; } inlines[64];
+    unsigned inline_count, inline_floor;
+    /* The block being inlined: its last return leaves its value at its call's height and says its kind here. */
+    struct { Kind *kind; int height; Type want; bool handler; } giving;
     unsigned whiles; /* while loops so far: each counts by a name of its own */
     unsigned settled; /* records' values calls gave, held by names of their own (027's 2c) */
     const struct Expr *given; /* the left side of the && or || whose right side faultless looks at */
@@ -1521,7 +1530,7 @@ static int input_index(const Compiler *c, const char *name) {
 }
 
 static int state_index(const Compiler *c, const char *name) {
-    for (unsigned i = c->local_count; i-- > 0;) /* a let's list, by the let's name */
+    for (unsigned i = c->local_count; i-- > c->local_floor;) /* a let's list, by the let's name */
         if (c->locals[i].list >= 0 && !strcmp(c->locals[i].name, name)) return c->locals[i].list;
     for (unsigned i = 0; i < c->states; ++i) if (!c->state_event[i] && !strcmp(c->state_names[i], name)) return (int)i;
     return -1;
@@ -2689,21 +2698,21 @@ static void app_name(Compiler *c, const Expr *e, const char *name) {
 /* The local named name in scope, or -1. */
 /* A let of a value (or a for's count) in scope, by name, or -1; a let's list is a state (state_index). */
 static int local_named(const Compiler *c, const char *name) {
-    for (unsigned i = c->local_count; i-- > 0;)
+    for (unsigned i = c->local_count; i-- > c->local_floor;)
         if (c->locals[i].list < 0 && !strcmp(c->locals[i].name, name)) return (int)i;
     return -1;
 }
 
 /* Any let in scope by name (a value's or a list's), or -1. */
 static int let_named(const Compiler *c, const char *name) {
-    for (unsigned i = c->local_count; i-- > 0;)
+    for (unsigned i = c->local_count; i-- > c->local_floor;)
         if (!strcmp(c->locals[i].name, name)) return (int)i;
     return -1;
 }
 
 /* The for (walk or count) in whose block a name is its item or count, innermost first, or -1. */
 static int loop_named(const Compiler *c, const char *name) {
-    for (unsigned i = c->loop_count; i-- > 0;)
+    for (unsigned i = c->loop_count; i-- > c->loop_floor;)
         if (!strcmp(c->loops[i].name, name)) return (int)i;
     return -1;
 }
@@ -2780,7 +2789,7 @@ static int loop_named(const Compiler *c, const char *name);
 static int parameter(const Compiler *c, const char *name);
 
 static int alias_named(const Compiler *c, const char *name) {
-    for (unsigned i = c->alias_count; i-- > 0;) if (!strcmp(c->aliases[i].name, name)) return (int)i;
+    for (unsigned i = c->alias_count; i-- > c->alias_floor;) if (!strcmp(c->aliases[i].name, name)) return (int)i;
     return -1;
 }
 
@@ -3139,6 +3148,32 @@ static void named_by(const Compiler *c, int module, const Stmt *s, uint64_t *out
 }
 
 /* Which fns run as functions: those with a block, and those a cycle of calls goes through. */
+/* Whether statements hold a for or a while, at any depth. */
+static bool holds_loop(const Stmt *s) {
+    for (; s; s = s->next)
+        if (s->kind == S_FOR || s->kind == S_COUNT || holds_loop(s->then) || holds_loop(s->otherwise)) return true;
+    return false;
+}
+
+/* Whether statements return only at their end (top: the block's own last statement, a return of one value at most)
+ * and hold no let's list (each call would want its own). */
+static bool returns_last(const Stmt *s, bool top) {
+    for (; s; s = s->next) {
+        if ((s->kind == S_RETURN && (!top || s->next)) || (s->kind == S_LET && s->listed)) return false;
+        if (!returns_last(s->then, false) || !returns_last(s->otherwise, false)) return false;
+    }
+    return true;
+}
+
+/* The type a typed fn's one result names when it is a number, an i64, a bool, bytes or text, or 0. */
+static Type result_type(const Fn *f) {
+    static const char *const names[] = {"number", "i64", "bool", "bytes", "text"};
+    static const Type types[] = {NUM, I64, BOOL, BYTES, TEXT};
+    for (unsigned k = 0; f->typed && f->result_count == 1 && !f->results[0].list && k < 5; ++k)
+        if (!strcmp(f->results[0].name, names[k])) return types[k];
+    return 0;
+}
+
 static void called_fns(Compiler *c) {
     uint64_t reach[FNS] = {0};
     for (unsigned i = 0; i < c->fn_count; ++i) {
@@ -3148,7 +3183,15 @@ static void called_fns(Compiler *c) {
     for (unsigned k = 0; k < c->fn_count; ++k)
         for (unsigned i = 0; i < c->fn_count; ++i)
             if (reach[i] >> k & 1) reach[i] |= reach[k];
-    for (unsigned i = 0; i < c->fn_count; ++i) c->fns[i].called = c->fns[i].block || (reach[i] >> i & 1);
+    for (unsigned i = 0; i < c->fn_count; ++i) {
+        Fn *f = &c->fns[i];
+        f->called = f->block || (reach[i] >> i & 1);
+        const Stmt *last = f->block;
+        while (last && last->next) last = last->next;
+        f->inlined = f->block && !(reach[i] >> i & 1) && returns_last(f->block, true) && (!f->typed || result_type(f)) &&
+                     (!last || last->kind != S_RETURN || !last->value || !last->value->next);
+        f->loops = holds_loop(f->block);
+    }
 }
 
 /* What making a function's code puts aside of what was being made. */
@@ -3159,6 +3202,7 @@ typedef struct {
     Scope *fold_scope, *scope;
     const Expr *given;
     unsigned local_count, local_slots, held_bytes, held_texts, alias_count, loop_count, nested, joins, code_count, code_capacity;
+    unsigned local_floor, loop_floor, alias_floor, inline_count, inline_floor;
     Type want;
     Kind returned;
     Op *code;
@@ -3167,8 +3211,8 @@ typedef struct {
 static void put_aside(Compiler *c, Aside *a, void *locals, void *aliases, void *loops) {
     *a = (Aside){c->message, c->arguments, c->row, c->fold_list, c->module, c->current, c->folding, c->filtering, c->handling,
                  c->has_returned, {0}, c->fold_scope, c->scope, c->given, c->local_count, c->local_slots, c->held_bytes,
-                 c->held_texts, c->alias_count, c->loop_count, c->nested, c->joins, c->code_count, c->code_capacity, c->want,
-                 c->returned, c->code};
+                 c->held_texts, c->alias_count, c->loop_count, c->nested, c->joins, c->code_count, c->code_capacity,
+                 c->local_floor, c->loop_floor, c->alias_floor, c->inline_count, c->inline_floor, c->want, c->returned, c->code};
     memcpy(a->fold_name, c->fold_name, NAME);
     memcpy(locals, c->locals, sizeof(c->locals));
     memcpy(aliases, c->aliases, sizeof(c->aliases));
@@ -3183,6 +3227,8 @@ static void take_back(Compiler *c, const Aside *a, const void *locals, const voi
     c->alias_count = a->alias_count, c->loop_count = a->loop_count, c->nested = a->nested, c->joins = a->joins;
     c->code_count = a->code_count, c->code_capacity = a->code_capacity, c->want = a->want, c->returned = a->returned;
     c->code = a->code;
+    c->local_floor = a->local_floor, c->loop_floor = a->loop_floor, c->alias_floor = a->alias_floor;
+    c->inline_count = a->inline_count, c->inline_floor = a->inline_floor;
     memcpy(c->fold_name, a->fold_name, NAME);
     memcpy(c->locals, locals, sizeof(c->locals));
     memcpy(c->aliases, aliases, sizeof(c->aliases));
@@ -3207,6 +3253,7 @@ static void make_instance(Compiler *c, int index) {
     c->message = c->arguments = c->row = c->fold_list = -1;
     c->folding = c->filtering = false, c->fold_scope = NULL, c->given = NULL, c->want = 0, c->joins = 0;
     c->local_count = c->local_slots = c->held_bytes = c->held_texts = c->alias_count = c->loop_count = c->nested = 0;
+    c->local_floor = c->loop_floor = c->alias_floor = 0, c->inline_floor = c->inline_count; /* its slots its own */
     for (unsigned i = 0; i < in->param_count; ++i) name_value(c, f->params[i], in->params[i], f->line, f->column);
     for (unsigned i = in->param_count; i-- > 0;) pop_named(c, local_named(c, f->params[i]), f->line, f->column);
     if (f->block) {
@@ -3257,11 +3304,86 @@ static int instance_of(Compiler *c, int fn, const Kind *kinds, unsigned count, c
 
 /* A call of a fn run as a function: its arguments onto the stack (an item's as its record's value), the CALL, and
  * what it gives (a value, several: TUPLE, or none). Arguments already given (count of them, kinds) are on the stack. */
+static void statements(Compiler *c, const Stmt *s);
+static void name_value(Compiler *c, const char *name, Kind kind, int line, int column);
+static void pop_named(Compiler *c, int local, int line, int column);
+
+/* A block fn inlined where it is called (Fn.inlined): its arguments stored into its parameters (each a let of its own),
+ * its statements and its last return made here, its names its own (the floors). Its slots are one set for each fn and
+ * kinds of arguments in a handler, used again at each call: Pure keeps a slot one kind, and a call's arguments are all
+ * made before its body stores them, so f(x, f(y)) is done with the inner body before the outer one begins. */
+static Kind inline_block(Compiler *c, const Expr *e, int fn, bool handler, int height) {
+    Fn *f = &c->fns[fn];
+    Kind kinds[PARAMS];
+    for (unsigned i = 0; i < f->param_count; ++i) {
+        kinds[i] = as_value(c, e->args[i], value(c, e->args[i], handler, height + (int)i));
+        Type t = kinds[i].type;
+        if (t != NUM && t != BOOL && t != I64 && t != BYTES && t != TEXT && t != LIST) /* a record's: called */
+            return call_fn(c, e, fn, handler, height, kinds, i + 1);
+    }
+    unsigned entry = c->inline_floor;
+    while (entry < c->inline_count &&
+           (c->inlines[entry].fn != fn || memcmp(c->inlines[entry].kinds, kinds, f->param_count * sizeof(Kind))))
+        ++entry;
+    if (entry == c->inline_count) {
+        if (entry == sizeof(c->inlines) / sizeof(c->inlines[0])) return call_fn(c, e, fn, handler, height, kinds, f->param_count);
+        c->inlines[entry].fn = fn;
+        memcpy(c->inlines[entry].kinds, kinds, f->param_count * sizeof(Kind));
+        c->inlines[entry].slots = c->local_slots, c->inlines[entry].bytes = c->held_bytes, c->inlines[entry].texts = c->held_texts;
+        ++c->inline_count;
+    }
+    unsigned slots = c->local_slots, bytes = c->held_bytes, texts = c->held_texts, locals = c->local_count;
+    unsigned floors[3] = {c->local_floor, c->loop_floor, c->alias_floor};
+    int message = c->message, arguments = c->arguments, row = c->row, module = c->module;
+    Scope *scope = c->scope;
+    c->local_slots = c->inlines[entry].slots, c->held_bytes = c->inlines[entry].bytes, c->held_texts = c->inlines[entry].texts;
+    c->local_floor = c->local_count, c->loop_floor = c->loop_count, c->alias_floor = c->alias_count;
+    c->message = c->arguments = c->row = -1, c->scope = NULL, c->module = f->module;
+    ++c->nested; /* its lets' lists begin empty at each call (c2) */
+    for (unsigned i = f->param_count; i-- > 0;) { /* the last argument is on top */
+        if (kinds[i].type == LIST) {
+            name_value(c, f->params[i], kinds[i], e->line, e->column);
+            pop_named(c, local_named(c, f->params[i]), e->line, e->column);
+            continue;
+        }
+        if (c->local_count == LOCALS || c->local_slots == LOCALS)
+            fail_at(c, e->line, e->column, "at most %d lets are allowed in a handler", LOCALS);
+        unsigned local = c->local_count++;
+        memset(&c->locals[local], 0, sizeof(c->locals[local]));
+        memcpy(c->locals[local].name, f->params[i], NAME);
+        c->locals[local].kind = kinds[i];
+        c->locals[local].list = -1;
+        if (kinds[i].type == BYTES || kinds[i].type == TEXT) {
+            unsigned *count = kinds[i].type == BYTES ? &c->held_bytes : &c->held_texts;
+            if (*count == HELD)
+                fail_at(c, e->line, e->column, "at most %d lets of %s are allowed in a handler", HELD, type_name(kinds[i].type));
+            c->locals[local].slot = (kinds[i].type == TEXT ? HELD_TEXT : 0) + (*count)++;
+            emit(c, e->line, e->column, BYTES_STORE, c->locals[local].slot, 0);
+        } else {
+            c->locals[local].slot = c->local_slots++;
+            emit(c, e->line, e->column, LOCAL_STORE, local_arg(c, (int)local), 0);
+        }
+    }
+    Kind result = {NOTHING, -1};
+    __typeof__(c->giving) giving = c->giving;
+    c->giving.kind = &result, c->giving.height = height, c->giving.want = result_type(f), c->giving.handler = handler;
+    statements(c, f->block); /* its return (its last line) leaves the value, its lets still there */
+    c->giving = giving;
+    --c->nested;
+    c->local_count = locals;
+    c->local_floor = floors[0], c->loop_floor = floors[1], c->alias_floor = floors[2];
+    c->message = message, c->arguments = arguments, c->row = row, c->scope = scope, c->module = module;
+    c->local_slots = c->local_slots > slots ? c->local_slots : slots;
+    c->held_bytes = c->held_bytes > bytes ? c->held_bytes : bytes, c->held_texts = c->held_texts > texts ? c->held_texts : texts;
+    return result;
+}
+
 static Kind call_fn(Compiler *c, const Expr *e, int fn, bool handler, int height, Kind *kinds, unsigned given) {
     const Fn *f = &c->fns[fn];
     unsigned count = e->kind == E_FN ? e->count : 0;
     if (count != f->param_count)
         fail_at(c, e->line, e->column, "%s expects %u argument%s", f->name, f->param_count, f->param_count == 1 ? "" : "s");
+    if (f->inlined && !given && !c->folding && !c->filtering && (!height || !f->loops)) return inline_block(c, e, fn, handler, height);
     if (c->folding) fail_at(c, e->line, e->column, "count, sum, max and min call no fn with a block, nor one that calls itself");
     if (!handler && c->current < 0)
         fail_at(c, e->line, e->column, "a view calls no fn with a block, nor one that calls itself: handlers do ('%s')", f->name);
@@ -5033,6 +5155,16 @@ static void statements(Compiler *c, const Stmt *s) {
             break;
         }
         case S_RETURN:
+            if (c->giving.kind) { /* an inlined block's last line: its value where its call is (inline_block) */
+                Kind *into = c->giving.kind;
+                Type want = c->giving.want;
+                const Expr *v = s->value;
+                c->giving.kind = NULL; /* (a block inlined in its value gives its own) */
+                if (v) *into = as_value(c, v, want == NUM || want == I64 ? wanted(c, v, c->giving.handler, c->giving.height, want)
+                                                                       : value(c, v, c->giving.handler, c->giving.height));
+                if (v && want && into->type != want) expect_kind(c, v, *into, want);
+                break;
+            }
             if (c->current >= 0) { /* what a fn's call gives (027's 2c) */
                 Instance *in = &c->instances[c->current];
                 const char *name = c->fns[in->fn].name;
@@ -5611,6 +5743,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
         Kind kind;
         if (c->view_body[i]) { /* statements as a handler's, changing only its lets and lists, and the value returned */
             c->local_count = c->local_slots = c->loop_count = c->nested = c->held_bytes = c->held_texts = 0;
+            c->inline_count = c->inline_floor = 0;
             c->handling = c->has_returned = false;
             statements(c, c->view_body[i]);
             kind = c->returned;
@@ -5633,6 +5766,7 @@ static void generate(Compiler *c, unsigned char **module, size_t *size) {
         int input = handler_input[i];
         c->message = input >= 0 && c->inputs[input].kind != KIND_KEY && c->inputs[input].kind != KIND_RELEASE ? input : -1;
         c->local_count = c->local_slots = c->loop_count = c->nested = c->held_bytes = c->held_texts = 0;
+        c->inline_count = c->inline_floor = 0;
         c->handler_code[i][0] = c->code_count;
         if (c->inputs[input].kind == KIND_POINT) { /* a pointing: its list's item at the row's position, the index's */
             const Input *in = &c->inputs[input];