@fraczak/k 6.4.0 → 6.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/Examples/arithmetics.k +86 -63
- package/Value.mjs +8 -2
- package/backends/arm64/src/runtime.c +20 -4
- package/backends/arm64/tests/integration.sh +1 -1
- package/backends/llvm/runtime/krt.c +387 -2
- package/backends/llvm/runtime/krt.h +1 -0
- package/backends/llvm/src/kvm2llvm.mjs +98 -29
- package/backends/llvm/tests/perf-support.mjs +1 -1
- package/backends/wasm/TECHNICAL-DEBT.md +11 -13
- package/backends/wasm/runtime.wat +375 -2
- package/backends/wasm/src/kvm2wasm.mjs +36 -0
- package/backends/wasm/src/wasm.mjs +48 -4
- package/backends/wasm/tests/test-wasm.mjs +8 -0
- package/browser/repl-browser.mjs +24 -7
- package/browser/tree-view.mjs +599 -0
- package/browser/vfs-data.mjs +3 -3
- package/codecs/runtime/show-value.mjs +10 -1
- package/package.json +1 -1
- package/repl.mjs +4 -0
- package/scripts/build-repl-html.mjs +301 -0
- package/scripts/perf-ieee.mjs +5 -2
- package/scripts/perf-int.mjs +331 -0
- package/scripts/run-tests.mjs +1 -0
- package/tests/test-repl-html.mjs +141 -0
- package/tests/test-tree-view.mjs +185 -0
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
#include <string.h>
|
|
6
6
|
|
|
7
7
|
enum {
|
|
8
|
-
K_ARENA_BLOCK_SIZE =
|
|
8
|
+
K_ARENA_BLOCK_SIZE = 4194304
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
typedef enum {
|
|
12
12
|
K_VALUE_UNIT,
|
|
13
13
|
K_VALUE_PRODUCT,
|
|
14
|
-
K_VALUE_VARIANT
|
|
14
|
+
K_VALUE_VARIANT,
|
|
15
|
+
K_VALUE_FORWARDED = 99
|
|
15
16
|
} k_value_kind;
|
|
16
17
|
|
|
17
18
|
typedef struct {
|
|
@@ -221,6 +222,390 @@ void k_rt_rewind(k_rt *rt, k_rt_checkpoint mark) {
|
|
|
221
222
|
rt->has_reusable_blocks = has_reusable;
|
|
222
223
|
}
|
|
223
224
|
|
|
225
|
+
typedef struct {
|
|
226
|
+
size_t *offsets;
|
|
227
|
+
size_t count;
|
|
228
|
+
size_t capacity;
|
|
229
|
+
} k_fixup_list;
|
|
230
|
+
|
|
231
|
+
static void fixup_add(k_fixup_list *list, size_t offset) {
|
|
232
|
+
if (list->count == list->capacity) {
|
|
233
|
+
size_t new_cap = list->capacity == 0 ? 1024 : list->capacity * 2;
|
|
234
|
+
size_t *new_offsets = (size_t *)realloc(list->offsets, new_cap * sizeof(size_t));
|
|
235
|
+
if (new_offsets == NULL) abort();
|
|
236
|
+
list->offsets = new_offsets;
|
|
237
|
+
list->capacity = new_cap;
|
|
238
|
+
}
|
|
239
|
+
list->offsets[list->count++] = offset;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
static int is_after_mark(k_rt *rt, const void *ptr, k_rt_checkpoint mark) {
|
|
243
|
+
if (rt == NULL || ptr == NULL) return 0;
|
|
244
|
+
const unsigned char *p = (const unsigned char *)ptr;
|
|
245
|
+
k_arena_block *mb = (k_arena_block *)mark.block;
|
|
246
|
+
if (mb == NULL) {
|
|
247
|
+
for (k_arena_block *b = rt->blocks; b != NULL; b = b->next) {
|
|
248
|
+
if (p >= b->data && p < b->data + b->capacity) {
|
|
249
|
+
return 1;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
if (rt->blocks == mb) {
|
|
255
|
+
return (p >= mb->data + mark.used && p < mb->data + mb->capacity);
|
|
256
|
+
}
|
|
257
|
+
for (k_arena_block *b = rt->blocks; b != NULL; b = b->next) {
|
|
258
|
+
if (b == mb) {
|
|
259
|
+
return (p >= b->data + mark.used && p < b->data + b->capacity);
|
|
260
|
+
}
|
|
261
|
+
if (p >= b->data && p < b->data + b->capacity) {
|
|
262
|
+
return 1;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static size_t bytes_since_mark(k_rt *rt, k_rt_checkpoint mark) {
|
|
269
|
+
if (rt == NULL || rt->blocks == NULL) return 0;
|
|
270
|
+
k_arena_block *mb = (k_arena_block *)mark.block;
|
|
271
|
+
if (rt->blocks == mb) {
|
|
272
|
+
return rt->blocks->used >= mark.used ? rt->blocks->used - mark.used : 0;
|
|
273
|
+
}
|
|
274
|
+
size_t total = 0;
|
|
275
|
+
if (mb == NULL) {
|
|
276
|
+
for (k_arena_block *b = rt->blocks; b != NULL; b = b->next) {
|
|
277
|
+
total += b->used;
|
|
278
|
+
}
|
|
279
|
+
return total;
|
|
280
|
+
}
|
|
281
|
+
for (k_arena_block *b = rt->blocks; b != NULL; b = b->next) {
|
|
282
|
+
if (b == mb) {
|
|
283
|
+
if (b->used > mark.used) {
|
|
284
|
+
total += (b->used - mark.used);
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
total += b->used;
|
|
289
|
+
}
|
|
290
|
+
return total;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static void *staging_alloc(unsigned char **staging_ptr, size_t *used, size_t *capacity, size_t size, k_fixup_list *fixups) {
|
|
294
|
+
size = align_size(size == 0 ? 1 : size);
|
|
295
|
+
if (*used + size > *capacity) {
|
|
296
|
+
size_t old_cap = *capacity;
|
|
297
|
+
size_t new_cap = (old_cap + size) * 2;
|
|
298
|
+
unsigned char *old_buf = *staging_ptr;
|
|
299
|
+
unsigned char *new_buf = (unsigned char *)realloc(old_buf, new_cap);
|
|
300
|
+
if (new_buf == NULL) abort();
|
|
301
|
+
if (new_buf != old_buf) {
|
|
302
|
+
ptrdiff_t realloc_diff = new_buf - old_buf;
|
|
303
|
+
for (size_t i = 0; i < fixups->count; i++) {
|
|
304
|
+
size_t off = fixups->offsets[i];
|
|
305
|
+
unsigned char **p = (unsigned char **)(new_buf + off);
|
|
306
|
+
*p += realloc_diff;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
*staging_ptr = new_buf;
|
|
310
|
+
*capacity = new_cap;
|
|
311
|
+
}
|
|
312
|
+
void *p = *staging_ptr + *used;
|
|
313
|
+
*used += size;
|
|
314
|
+
return p;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
static k_value *copy_value(k_rt *rt, k_value *root, unsigned char **staging_ptr, size_t *staging_used, size_t *staging_cap, k_fixup_list *fixups, k_rt_checkpoint mark) {
|
|
318
|
+
if (root == NULL || !is_after_mark(rt, root, mark)) {
|
|
319
|
+
return root;
|
|
320
|
+
}
|
|
321
|
+
if ((int)root->kind == K_VALUE_FORWARDED) {
|
|
322
|
+
return root->as.variant.payload;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (root->kind == K_VALUE_VARIANT) {
|
|
326
|
+
k_value *first_staged = NULL;
|
|
327
|
+
k_value *prev_staged = NULL;
|
|
328
|
+
k_value *curr = root;
|
|
329
|
+
|
|
330
|
+
while (curr != NULL) {
|
|
331
|
+
if (!is_after_mark(rt, curr, mark)) {
|
|
332
|
+
if (prev_staged != NULL) {
|
|
333
|
+
prev_staged->as.variant.payload = curr;
|
|
334
|
+
}
|
|
335
|
+
if (first_staged == NULL) {
|
|
336
|
+
first_staged = curr;
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if ((int)curr->kind == K_VALUE_FORWARDED) {
|
|
342
|
+
k_value *staged_target = curr->as.variant.payload;
|
|
343
|
+
if (prev_staged != NULL) {
|
|
344
|
+
prev_staged->as.variant.payload = staged_target;
|
|
345
|
+
fixup_add(fixups, (unsigned char *)&prev_staged->as.variant.payload - *staging_ptr);
|
|
346
|
+
}
|
|
347
|
+
if (first_staged == NULL) {
|
|
348
|
+
first_staged = staged_target;
|
|
349
|
+
}
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (curr->as.variant.tag != NULL) {
|
|
354
|
+
if (bytes_equal(curr->as.variant.tag, curr->as.variant.tag_length, "Float64", 7)) {
|
|
355
|
+
k_value *staged_v = (k_value *)staging_alloc(staging_ptr, staging_used, staging_cap, sizeof(k_value), fixups);
|
|
356
|
+
staged_v->kind = K_VALUE_VARIANT;
|
|
357
|
+
staged_v->rt = rt;
|
|
358
|
+
staged_v->as.variant.tag = curr->as.variant.tag;
|
|
359
|
+
staged_v->as.variant.tag_length = curr->as.variant.tag_length;
|
|
360
|
+
staged_v->as.variant.payload = NULL;
|
|
361
|
+
|
|
362
|
+
if (is_after_mark(rt, curr->as.variant.tag, mark)) {
|
|
363
|
+
char *staged_tag = (char *)staging_alloc(staging_ptr, staging_used, staging_cap, curr->as.variant.tag_length + 1, fixups);
|
|
364
|
+
memcpy(staged_tag, curr->as.variant.tag, curr->as.variant.tag_length);
|
|
365
|
+
staged_tag[curr->as.variant.tag_length] = 0;
|
|
366
|
+
staged_v->as.variant.tag = staged_tag;
|
|
367
|
+
fixup_add(fixups, (unsigned char *)&staged_v->as.variant.tag - *staging_ptr);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (curr->as.variant.payload != NULL && is_after_mark(rt, curr->as.variant.payload, mark)) {
|
|
371
|
+
void *raw = staging_alloc(staging_ptr, staging_used, staging_cap, sizeof(double), fixups);
|
|
372
|
+
memcpy(raw, curr->as.variant.payload, sizeof(double));
|
|
373
|
+
staged_v->as.variant.payload = (k_value *)raw;
|
|
374
|
+
fixup_add(fixups, (unsigned char *)&staged_v->as.variant.payload - *staging_ptr);
|
|
375
|
+
} else {
|
|
376
|
+
staged_v->as.variant.payload = curr->as.variant.payload;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (prev_staged != NULL) {
|
|
380
|
+
prev_staged->as.variant.payload = staged_v;
|
|
381
|
+
fixup_add(fixups, (unsigned char *)&prev_staged->as.variant.payload - *staging_ptr);
|
|
382
|
+
}
|
|
383
|
+
if (first_staged == NULL) {
|
|
384
|
+
first_staged = staged_v;
|
|
385
|
+
}
|
|
386
|
+
curr->kind = (k_value_kind)K_VALUE_FORWARDED;
|
|
387
|
+
curr->as.variant.payload = staged_v;
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (bytes_equal(curr->as.variant.tag, curr->as.variant.tag_length, "Int32", 5)) {
|
|
392
|
+
k_value *staged_v = (k_value *)staging_alloc(staging_ptr, staging_used, staging_cap, sizeof(k_value), fixups);
|
|
393
|
+
staged_v->kind = K_VALUE_VARIANT;
|
|
394
|
+
staged_v->rt = rt;
|
|
395
|
+
staged_v->as.variant.tag = curr->as.variant.tag;
|
|
396
|
+
staged_v->as.variant.tag_length = curr->as.variant.tag_length;
|
|
397
|
+
staged_v->as.variant.payload = NULL;
|
|
398
|
+
|
|
399
|
+
if (is_after_mark(rt, curr->as.variant.tag, mark)) {
|
|
400
|
+
char *staged_tag = (char *)staging_alloc(staging_ptr, staging_used, staging_cap, curr->as.variant.tag_length + 1, fixups);
|
|
401
|
+
memcpy(staged_tag, curr->as.variant.tag, curr->as.variant.tag_length);
|
|
402
|
+
staged_tag[curr->as.variant.tag_length] = 0;
|
|
403
|
+
staged_v->as.variant.tag = staged_tag;
|
|
404
|
+
fixup_add(fixups, (unsigned char *)&staged_v->as.variant.tag - *staging_ptr);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (curr->as.variant.payload != NULL && is_after_mark(rt, curr->as.variant.payload, mark)) {
|
|
408
|
+
void *raw = staging_alloc(staging_ptr, staging_used, staging_cap, sizeof(int32_t), fixups);
|
|
409
|
+
memcpy(raw, curr->as.variant.payload, sizeof(int32_t));
|
|
410
|
+
staged_v->as.variant.payload = (k_value *)raw;
|
|
411
|
+
fixup_add(fixups, (unsigned char *)&staged_v->as.variant.payload - *staging_ptr);
|
|
412
|
+
} else {
|
|
413
|
+
staged_v->as.variant.payload = curr->as.variant.payload;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (prev_staged != NULL) {
|
|
417
|
+
prev_staged->as.variant.payload = staged_v;
|
|
418
|
+
fixup_add(fixups, (unsigned char *)&prev_staged->as.variant.payload - *staging_ptr);
|
|
419
|
+
}
|
|
420
|
+
if (first_staged == NULL) {
|
|
421
|
+
first_staged = staged_v;
|
|
422
|
+
}
|
|
423
|
+
curr->kind = (k_value_kind)K_VALUE_FORWARDED;
|
|
424
|
+
curr->as.variant.payload = staged_v;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (curr->kind != K_VALUE_VARIANT) {
|
|
430
|
+
k_value *staged_child = copy_value(rt, curr, staging_ptr, staging_used, staging_cap, fixups, mark);
|
|
431
|
+
if (prev_staged != NULL) {
|
|
432
|
+
prev_staged->as.variant.payload = staged_child;
|
|
433
|
+
if (staged_child != NULL && (unsigned char *)staged_child >= *staging_ptr && (unsigned char *)staged_child < *staging_ptr + *staging_used) {
|
|
434
|
+
fixup_add(fixups, (unsigned char *)&prev_staged->as.variant.payload - *staging_ptr);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (first_staged == NULL) {
|
|
438
|
+
first_staged = staged_child;
|
|
439
|
+
}
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
k_value *staged_v = (k_value *)staging_alloc(staging_ptr, staging_used, staging_cap, sizeof(k_value), fixups);
|
|
444
|
+
staged_v->kind = K_VALUE_VARIANT;
|
|
445
|
+
staged_v->rt = rt;
|
|
446
|
+
staged_v->as.variant.tag = curr->as.variant.tag;
|
|
447
|
+
staged_v->as.variant.tag_length = curr->as.variant.tag_length;
|
|
448
|
+
staged_v->as.variant.payload = NULL;
|
|
449
|
+
|
|
450
|
+
if (curr->as.variant.tag != NULL && is_after_mark(rt, curr->as.variant.tag, mark)) {
|
|
451
|
+
char *staged_tag = (char *)staging_alloc(staging_ptr, staging_used, staging_cap, curr->as.variant.tag_length + 1, fixups);
|
|
452
|
+
memcpy(staged_tag, curr->as.variant.tag, curr->as.variant.tag_length);
|
|
453
|
+
staged_tag[curr->as.variant.tag_length] = 0;
|
|
454
|
+
staged_v->as.variant.tag = staged_tag;
|
|
455
|
+
fixup_add(fixups, (unsigned char *)&staged_v->as.variant.tag - *staging_ptr);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (prev_staged != NULL) {
|
|
459
|
+
prev_staged->as.variant.payload = staged_v;
|
|
460
|
+
fixup_add(fixups, (unsigned char *)&prev_staged->as.variant.payload - *staging_ptr);
|
|
461
|
+
}
|
|
462
|
+
if (first_staged == NULL) {
|
|
463
|
+
first_staged = staged_v;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
k_value *next = curr->as.variant.payload;
|
|
467
|
+
curr->kind = (k_value_kind)K_VALUE_FORWARDED;
|
|
468
|
+
curr->as.variant.payload = staged_v;
|
|
469
|
+
|
|
470
|
+
prev_staged = staged_v;
|
|
471
|
+
curr = next;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return first_staged;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
size_t header_size = align_size(sizeof(k_value));
|
|
478
|
+
size_t fields_size = root->as.product.count * sizeof(k_field);
|
|
479
|
+
size_t total_size = header_size + fields_size;
|
|
480
|
+
|
|
481
|
+
k_value *staged_p = (k_value *)staging_alloc(staging_ptr, staging_used, staging_cap, total_size, fixups);
|
|
482
|
+
staged_p->kind = root->kind;
|
|
483
|
+
staged_p->rt = rt;
|
|
484
|
+
staged_p->as.product.count = root->as.product.count;
|
|
485
|
+
staged_p->as.product.capacity = root->as.product.count;
|
|
486
|
+
|
|
487
|
+
if (root->as.product.count > 0) {
|
|
488
|
+
k_field *staged_fields = (k_field *)((unsigned char *)staged_p + header_size);
|
|
489
|
+
staged_p->as.product.fields = staged_fields;
|
|
490
|
+
fixup_add(fixups, (unsigned char *)&staged_p->as.product.fields - *staging_ptr);
|
|
491
|
+
|
|
492
|
+
k_field *old_fields = root->as.product.fields;
|
|
493
|
+
size_t count = root->as.product.count;
|
|
494
|
+
|
|
495
|
+
root->kind = (k_value_kind)K_VALUE_FORWARDED;
|
|
496
|
+
root->as.variant.payload = staged_p;
|
|
497
|
+
|
|
498
|
+
for (size_t i = 0; i < count; i++) {
|
|
499
|
+
staged_fields[i].label_length = old_fields[i].label_length;
|
|
500
|
+
staged_fields[i].label = old_fields[i].label;
|
|
501
|
+
if (old_fields[i].label != NULL && is_after_mark(rt, old_fields[i].label, mark)) {
|
|
502
|
+
char *staged_label = (char *)staging_alloc(staging_ptr, staging_used, staging_cap, old_fields[i].label_length + 1, fixups);
|
|
503
|
+
memcpy(staged_label, old_fields[i].label, old_fields[i].label_length);
|
|
504
|
+
staged_label[old_fields[i].label_length] = 0;
|
|
505
|
+
staged_fields[i].label = staged_label;
|
|
506
|
+
fixup_add(fixups, (unsigned char *)&staged_fields[i].label - *staging_ptr);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
k_value *child = copy_value(rt, old_fields[i].value, staging_ptr, staging_used, staging_cap, fixups, mark);
|
|
510
|
+
staged_fields[i].value = child;
|
|
511
|
+
if (child != NULL && (unsigned char *)child >= *staging_ptr && (unsigned char *)child < *staging_ptr + *staging_used) {
|
|
512
|
+
fixup_add(fixups, (unsigned char *)&staged_fields[i].value - *staging_ptr);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
} else {
|
|
516
|
+
staged_p->as.product.fields = NULL;
|
|
517
|
+
root->kind = (k_value_kind)K_VALUE_FORWARDED;
|
|
518
|
+
root->as.variant.payload = staged_p;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return staged_p;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
static void rt_rewind_and_free(k_rt *rt, k_rt_checkpoint mark) {
|
|
525
|
+
if (rt == NULL) return;
|
|
526
|
+
if (mark.block == NULL) {
|
|
527
|
+
k_arena_block *b = rt->blocks;
|
|
528
|
+
while (b != NULL) {
|
|
529
|
+
k_arena_block *next = b->next;
|
|
530
|
+
free(b);
|
|
531
|
+
b = next;
|
|
532
|
+
}
|
|
533
|
+
rt->blocks = NULL;
|
|
534
|
+
rt->unit_cache = NULL;
|
|
535
|
+
rt->bit0_cache = NULL;
|
|
536
|
+
rt->bit1_cache = NULL;
|
|
537
|
+
rt->has_reusable_blocks = 0;
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
k_arena_block *b = rt->blocks;
|
|
541
|
+
while (b != NULL && b != (k_arena_block *)mark.block) {
|
|
542
|
+
k_arena_block *next = b->next;
|
|
543
|
+
free(b);
|
|
544
|
+
b = next;
|
|
545
|
+
}
|
|
546
|
+
rt->blocks = (k_arena_block *)mark.block;
|
|
547
|
+
if (rt->blocks != NULL) {
|
|
548
|
+
rt->blocks->used = mark.used;
|
|
549
|
+
}
|
|
550
|
+
rt->unit_cache = NULL;
|
|
551
|
+
rt->bit0_cache = NULL;
|
|
552
|
+
rt->bit1_cache = NULL;
|
|
553
|
+
rt->has_reusable_blocks = 0;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_checkpoint *mark_ptr) {
|
|
557
|
+
if (rt == NULL || root == NULL || mark_ptr == NULL) return root;
|
|
558
|
+
|
|
559
|
+
size_t bytes = bytes_since_mark(rt, *mark_ptr);
|
|
560
|
+
if (bytes < 4194304) {
|
|
561
|
+
return root;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (!is_after_mark(rt, root, *mark_ptr)) {
|
|
565
|
+
rt_rewind_and_free(rt, *mark_ptr);
|
|
566
|
+
return root;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
size_t staging_cap = bytes + 65536;
|
|
570
|
+
unsigned char *staging = (unsigned char *)malloc(staging_cap);
|
|
571
|
+
if (staging == NULL) abort();
|
|
572
|
+
size_t staging_used = 0;
|
|
573
|
+
|
|
574
|
+
k_fixup_list fixups = {NULL, 0, 0};
|
|
575
|
+
|
|
576
|
+
k_value *staged_root = copy_value(rt, root, &staging, &staging_used, &staging_cap, &fixups, *mark_ptr);
|
|
577
|
+
|
|
578
|
+
rt_rewind_and_free(rt, *mark_ptr);
|
|
579
|
+
|
|
580
|
+
if (staging_used == 0) {
|
|
581
|
+
free(staging);
|
|
582
|
+
if (fixups.offsets != NULL) free(fixups.offsets);
|
|
583
|
+
return staged_root;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
void *arena_dest = rt_alloc(rt, staging_used);
|
|
587
|
+
if (arena_dest == NULL) abort();
|
|
588
|
+
|
|
589
|
+
ptrdiff_t diff = (unsigned char *)arena_dest - staging;
|
|
590
|
+
|
|
591
|
+
for (size_t i = 0; i < fixups.count; i++) {
|
|
592
|
+
size_t off = fixups.offsets[i];
|
|
593
|
+
unsigned char **p = (unsigned char **)(staging + off);
|
|
594
|
+
*p += diff;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
memcpy(arena_dest, staging, staging_used);
|
|
598
|
+
|
|
599
|
+
k_value *new_root = (staged_root != NULL && (unsigned char *)staged_root >= staging && (unsigned char *)staged_root < staging + staging_used)
|
|
600
|
+
? (k_value *)((unsigned char *)staged_root + diff)
|
|
601
|
+
: staged_root;
|
|
602
|
+
|
|
603
|
+
free(staging);
|
|
604
|
+
if (fixups.offsets != NULL) free(fixups.offsets);
|
|
605
|
+
|
|
606
|
+
return new_root;
|
|
607
|
+
}
|
|
608
|
+
|
|
224
609
|
void k_rt_free(k_rt *rt) {
|
|
225
610
|
if (rt == NULL) return;
|
|
226
611
|
k_arena_block *block = rt->blocks;
|
|
@@ -66,6 +66,7 @@ k_rt *k_rt_new(void);
|
|
|
66
66
|
void k_rt_reset(k_rt *rt);
|
|
67
67
|
k_rt_checkpoint k_rt_mark(k_rt *rt);
|
|
68
68
|
void k_rt_rewind(k_rt *rt, k_rt_checkpoint mark);
|
|
69
|
+
k_value *k_rt_compact(k_rt *rt, k_value *root, k_rt_checkpoint *mark_ptr);
|
|
69
70
|
void *k_rt_alloc(k_rt *rt, size_t size);
|
|
70
71
|
void k_rt_free(k_rt *rt);
|
|
71
72
|
|
|
@@ -53,6 +53,7 @@ export function runtimeDeclarations() {
|
|
|
53
53
|
"",
|
|
54
54
|
"declare %k_rt_mark @k_rt_mark(ptr)",
|
|
55
55
|
"declare void @k_rt_rewind(ptr, %k_rt_mark)",
|
|
56
|
+
"declare ptr @k_rt_compact(ptr, ptr, ptr)",
|
|
56
57
|
"declare ptr @k_rt_alloc(ptr, i64)",
|
|
57
58
|
"declare ptr @k_unit(ptr)",
|
|
58
59
|
"declare ptr @k_bit0(ptr)",
|
|
@@ -146,10 +147,11 @@ function lowerRawVariant(ctx, tag, payload) {
|
|
|
146
147
|
return variant;
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
function lowerProductGetAt(ctx, input, edgeIndex) {
|
|
150
|
+
function lowerProductGetAt(ctx, input, edgeIndex, label = null) {
|
|
150
151
|
const nonNullBlock = ctx.blockName("product_non_null");
|
|
151
152
|
const productBlock = ctx.blockName("product_kind");
|
|
152
153
|
const fieldBlock = ctx.blockName("product_field");
|
|
154
|
+
const fallbackBlock = ctx.blockName("product_fallback");
|
|
153
155
|
const missingBlock = ctx.blockName("product_missing");
|
|
154
156
|
const doneBlock = ctx.blockName("product_done");
|
|
155
157
|
const isMissing = ctx.tempName("product_is_missing");
|
|
@@ -172,16 +174,38 @@ function lowerProductGetAt(ctx, input, edgeIndex) {
|
|
|
172
174
|
const fields = loadPtrAt(ctx, input, K_VALUE_PRODUCT_FIELDS_OFFSET, "product_fields_ptr");
|
|
173
175
|
const slot = bytePtr(ctx, fields, edgeIndex * K_FIELD_SIZE, "field_slot");
|
|
174
176
|
const value = loadPtrAt(ctx, slot, K_FIELD_VALUE_OFFSET, "field");
|
|
175
|
-
ctx.lines.push(` br label %${doneBlock}`);
|
|
176
177
|
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
if (label) {
|
|
179
|
+
const fLabel = loadPtrAt(ctx, slot, K_FIELD_LABEL_OFFSET, "field_label");
|
|
180
|
+
const labelMatch = ctx.tempName("label_match");
|
|
181
|
+
ctx.lines.push(` ${labelMatch} = icmp eq ptr ${fLabel}, ${label.pointer}`);
|
|
182
|
+
ctx.lines.push(` br i1 ${labelMatch}, label %${doneBlock}, label %${fallbackBlock}`);
|
|
183
|
+
|
|
184
|
+
ctx.lines.push(`${fallbackBlock}:`);
|
|
185
|
+
const fallbackVal = ctx.tempName("fallback_field");
|
|
186
|
+
ctx.lines.push(` ${fallbackVal} = call ptr @k_product_get_n(ptr ${input}, ptr ${label.pointer}, i64 ${label.length})`);
|
|
187
|
+
ctx.lines.push(` br label %${doneBlock}`);
|
|
188
|
+
|
|
189
|
+
ctx.lines.push(`${missingBlock}:`);
|
|
190
|
+
ctx.lines.push(` br label %${doneBlock}`);
|
|
191
|
+
|
|
192
|
+
ctx.lines.push(`${doneBlock}:`);
|
|
193
|
+
const resultValue = ctx.tempName("field_value");
|
|
194
|
+
ctx.lines.push(` ${resultValue} = phi ptr [${value}, %${fieldBlock}], [${fallbackVal}, %${fallbackBlock}], [null, %${missingBlock}]`);
|
|
195
|
+
ctx.currentBlock = doneBlock;
|
|
196
|
+
return resultValue;
|
|
197
|
+
} else {
|
|
198
|
+
ctx.lines.push(` br label %${doneBlock}`);
|
|
179
199
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
200
|
+
ctx.lines.push(`${missingBlock}:`);
|
|
201
|
+
ctx.lines.push(` br label %${doneBlock}`);
|
|
202
|
+
|
|
203
|
+
ctx.lines.push(`${doneBlock}:`);
|
|
204
|
+
const resultValue = ctx.tempName("field_value");
|
|
205
|
+
ctx.lines.push(` ${resultValue} = phi ptr [${value}, %${fieldBlock}], [null, %${missingBlock}]`);
|
|
206
|
+
ctx.currentBlock = doneBlock;
|
|
207
|
+
return resultValue;
|
|
208
|
+
}
|
|
185
209
|
}
|
|
186
210
|
|
|
187
211
|
function lowerVariantMatchBranch(ctx, input, label, matchBlock, mismatchBlock) {
|
|
@@ -287,6 +311,56 @@ function lowerRtRewind(ctx, mark) {
|
|
|
287
311
|
ctx.currentBlock = doneBlock;
|
|
288
312
|
}
|
|
289
313
|
|
|
314
|
+
function lowerRtCompact(ctx, val, tailMarkSlot = "%tail_mark_slot") {
|
|
315
|
+
const curBlock = loadPtrAt(ctx, "%rt", K_RT_BLOCKS_OFFSET, "cur_block");
|
|
316
|
+
const hasBlock = ctx.tempName("has_block");
|
|
317
|
+
const markBlockPtr = ctx.tempName("mark_block_ptr");
|
|
318
|
+
ctx.lines.push(` ${hasBlock} = icmp ne ptr ${curBlock}, null`);
|
|
319
|
+
ctx.lines.push(` ${markBlockPtr} = getelementptr inbounds %k_rt_mark, ptr ${tailMarkSlot}, i32 0, i32 0`);
|
|
320
|
+
const markBlock = ctx.tempName("mark_block");
|
|
321
|
+
ctx.lines.push(` ${markBlock} = load ptr, ptr ${markBlockPtr}`);
|
|
322
|
+
|
|
323
|
+
const diffBlock = ctx.tempName("diff_block");
|
|
324
|
+
ctx.lines.push(` ${diffBlock} = icmp ne ptr ${curBlock}, ${markBlock}`);
|
|
325
|
+
const needCheck = ctx.tempName("need_growth_check");
|
|
326
|
+
ctx.lines.push(` ${needCheck} = and i1 ${hasBlock}, ${diffBlock}`);
|
|
327
|
+
|
|
328
|
+
const checkGrowthBlock = ctx.blockName("check_growth");
|
|
329
|
+
const doCompactBlock = ctx.blockName("do_compact");
|
|
330
|
+
const doneBlock = ctx.blockName("compact_done");
|
|
331
|
+
|
|
332
|
+
ctx.lines.push(` br i1 ${needCheck}, label %${doCompactBlock}, label %${checkGrowthBlock}`);
|
|
333
|
+
|
|
334
|
+
ctx.lines.push(`${checkGrowthBlock}:`);
|
|
335
|
+
const checkBlockHasBlock = ctx.tempName("check_has_block");
|
|
336
|
+
ctx.lines.push(` ${checkBlockHasBlock} = icmp ne ptr ${curBlock}, null`);
|
|
337
|
+
const loadGrowthBlock = ctx.blockName("load_growth");
|
|
338
|
+
ctx.lines.push(` br i1 ${checkBlockHasBlock}, label %${loadGrowthBlock}, label %${doneBlock}`);
|
|
339
|
+
|
|
340
|
+
ctx.lines.push(`${loadGrowthBlock}:`);
|
|
341
|
+
const curUsed = loadI64At(ctx, curBlock, K_ARENA_BLOCK_USED_OFFSET, "cur_used");
|
|
342
|
+
const markUsedPtr = ctx.tempName("mark_used_ptr");
|
|
343
|
+
ctx.lines.push(` ${markUsedPtr} = getelementptr inbounds %k_rt_mark, ptr ${tailMarkSlot}, i32 0, i32 1`);
|
|
344
|
+
const markUsed = ctx.tempName("mark_used");
|
|
345
|
+
ctx.lines.push(` ${markUsed} = load i64, ptr ${markUsedPtr}`);
|
|
346
|
+
const growth = ctx.tempName("arena_growth");
|
|
347
|
+
ctx.lines.push(` ${growth} = sub i64 ${curUsed}, ${markUsed}`);
|
|
348
|
+
const threshExceeded = ctx.tempName("thresh_exceeded");
|
|
349
|
+
ctx.lines.push(` ${threshExceeded} = icmp ugt i64 ${growth}, 4194304`);
|
|
350
|
+
ctx.lines.push(` br i1 ${threshExceeded}, label %${doCompactBlock}, label %${doneBlock}`);
|
|
351
|
+
|
|
352
|
+
ctx.lines.push(`${doCompactBlock}:`);
|
|
353
|
+
const compacted = ctx.tempName("compacted");
|
|
354
|
+
ctx.lines.push(` ${compacted} = call ptr @k_rt_compact(ptr %rt, ptr ${val}, ptr ${tailMarkSlot})`);
|
|
355
|
+
ctx.lines.push(` br label %${doneBlock}`);
|
|
356
|
+
|
|
357
|
+
ctx.lines.push(`${doneBlock}:`);
|
|
358
|
+
const finalVal = ctx.tempName("compact_res");
|
|
359
|
+
ctx.lines.push(` ${finalVal} = phi ptr [${val}, %${checkGrowthBlock}], [${val}, %${loadGrowthBlock}], [${compacted}, %${doCompactBlock}]`);
|
|
360
|
+
ctx.currentBlock = doneBlock;
|
|
361
|
+
return finalVal;
|
|
362
|
+
}
|
|
363
|
+
|
|
290
364
|
function nullCheck(ctx, val, failTarget) {
|
|
291
365
|
const isMissing = ctx.tempName("missing");
|
|
292
366
|
const okBlock = ctx.blockName("nonnull");
|
|
@@ -322,7 +396,8 @@ function statusCheck(ctx, callResult, failTarget = "func_fail") {
|
|
|
322
396
|
if (ctx.catchTail) {
|
|
323
397
|
const tailInput = ctx.tempName("tail_arg");
|
|
324
398
|
ctx.lines.push(` ${tailInput} = extractvalue %k_result ${callResult}, 1`);
|
|
325
|
-
ctx
|
|
399
|
+
const compacted = lowerRtCompact(ctx, tailInput, "%tail_mark_slot");
|
|
400
|
+
ctx.lines.push(` store ptr ${compacted}, ptr %tail_input_slot`);
|
|
326
401
|
ctx.lines.push(" br label %tail_loop");
|
|
327
402
|
} else {
|
|
328
403
|
ctx.lines.push(` ret %k_result ${callResult}`);
|
|
@@ -366,8 +441,7 @@ function hasSelfTailCall(insts, funcName, isTail = true) {
|
|
|
366
441
|
if (inst.branches) {
|
|
367
442
|
const unionIsTail = isTail && tailValueAfter(insts, i + 1, inst.dest);
|
|
368
443
|
for (let bIdx = 0; bIdx < inst.branches.length; bIdx++) {
|
|
369
|
-
|
|
370
|
-
if (hasSelfTailCall(inst.branches[bIdx].body, funcName, unionIsTail && isLast)) return true;
|
|
444
|
+
if (hasSelfTailCall(inst.branches[bIdx].body, funcName, unionIsTail)) return true;
|
|
371
445
|
}
|
|
372
446
|
}
|
|
373
447
|
}
|
|
@@ -384,13 +458,6 @@ function getFieldEdgeIndex(inst, regPatterns, kvmFunc) {
|
|
|
384
458
|
if (idx >= 0) return idx;
|
|
385
459
|
}
|
|
386
460
|
}
|
|
387
|
-
if (Array.isArray(inst.pattern) && inst.pattern.length > 0) {
|
|
388
|
-
const root = inst.pattern[0];
|
|
389
|
-
if ((root[0] === "open-product" || root[0] === "closed-product") && Array.isArray(root[1])) {
|
|
390
|
-
const idx = root[1].findIndex(([l]) => l === inst.label);
|
|
391
|
-
if (idx >= 0) return idx;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
461
|
return -1;
|
|
395
462
|
}
|
|
396
463
|
|
|
@@ -538,7 +605,11 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
|
|
|
538
605
|
|
|
539
606
|
if (isCatchTail) {
|
|
540
607
|
ctx.allocas.push(" %tail_input_slot = alloca ptr");
|
|
608
|
+
ctx.allocas.push(" %tail_mark_slot = alloca %k_rt_mark");
|
|
541
609
|
ctx.lines.push(" store ptr %input, ptr %tail_input_slot");
|
|
610
|
+
const initMark = ctx.tempName("init_mark");
|
|
611
|
+
ctx.lines.push(` ${initMark} = call %k_rt_mark @k_rt_mark(ptr %rt)`);
|
|
612
|
+
ctx.lines.push(` store %k_rt_mark ${initMark}, ptr %tail_mark_slot`);
|
|
542
613
|
ctx.lines.push(" br label %tail_loop");
|
|
543
614
|
ctx.lines.push("tail_loop:");
|
|
544
615
|
ctx.lines.push(" %tail_input = load ptr, ptr %tail_input_slot");
|
|
@@ -598,17 +669,13 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
|
|
|
598
669
|
const input = getReg(inst.src);
|
|
599
670
|
nullCheck(ctx, input, failTarget);
|
|
600
671
|
const edgeIndex = getFieldEdgeIndex(inst, regPatterns, kvmFunc);
|
|
672
|
+
const label = ctx.labelRef(inst.label);
|
|
601
673
|
let val;
|
|
602
674
|
if (edgeIndex >= 0 && ctx.runtimeMode !== "compact") {
|
|
603
|
-
val = lowerProductGetAt(ctx, input, edgeIndex);
|
|
675
|
+
val = lowerProductGetAt(ctx, input, edgeIndex, label);
|
|
604
676
|
} else {
|
|
605
|
-
const label = ctx.labelRef(inst.label);
|
|
606
677
|
val = ctx.tempName("field");
|
|
607
|
-
|
|
608
|
-
ctx.lines.push(` ${val} = call ptr @k_product_get_at(ptr ${input}, i64 ${edgeIndex})`);
|
|
609
|
-
} else {
|
|
610
|
-
ctx.lines.push(` ${val} = call ptr @k_product_get_n(ptr ${input}, ptr ${label.pointer}, i64 ${label.length})`);
|
|
611
|
-
}
|
|
678
|
+
ctx.lines.push(` ${val} = call ptr @k_product_get_n(ptr ${input}, ptr ${label.pointer}, i64 ${label.length})`);
|
|
612
679
|
}
|
|
613
680
|
nullCheck(ctx, val, failTarget);
|
|
614
681
|
regValues.set(cleanReg(inst.dest), val);
|
|
@@ -656,7 +723,8 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
|
|
|
656
723
|
const v = getReg(inst.src);
|
|
657
724
|
if (isSelfTailCall(insts, i, tailRef)) {
|
|
658
725
|
if (ctx.catchTail) {
|
|
659
|
-
ctx
|
|
726
|
+
const compacted = lowerRtCompact(ctx, v, "%tail_mark_slot");
|
|
727
|
+
ctx.lines.push(` store ptr ${compacted}, ptr %tail_input_slot`);
|
|
660
728
|
ctx.lines.push(" br label %tail_loop");
|
|
661
729
|
return;
|
|
662
730
|
} else {
|
|
@@ -796,7 +864,7 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
|
|
|
796
864
|
outputPattern: kvmFunc.outputPattern,
|
|
797
865
|
body: branch.body
|
|
798
866
|
};
|
|
799
|
-
const armTailRef =
|
|
867
|
+
const armTailRef = tailValueAfter(insts, i + 1, inst.dest) ? tailRef : null;
|
|
800
868
|
const armBody = lowerKVMFunction(armFunc, armName, armFunc.name, moduleCtx, "internal", { ...options, tailRef: armTailRef });
|
|
801
869
|
moduleCtx.syntheticFunctions.push(armBody);
|
|
802
870
|
|
|
@@ -818,7 +886,8 @@ function lowerKVMFunction(kvmFunc, symbol, funcName, moduleCtx, linkage = "", op
|
|
|
818
886
|
if (ctx.catchTail) {
|
|
819
887
|
const tailArg = ctx.tempName("arm_tail_arg");
|
|
820
888
|
ctx.lines.push(` ${tailArg} = extractvalue %k_result ${armCall}, 1`);
|
|
821
|
-
ctx
|
|
889
|
+
const compacted = lowerRtCompact(ctx, tailArg, "%tail_mark_slot");
|
|
890
|
+
ctx.lines.push(` store ptr ${compacted}, ptr %tail_input_slot`);
|
|
822
891
|
ctx.lines.push(" br label %tail_loop");
|
|
823
892
|
} else {
|
|
824
893
|
ctx.lines.push(` ret %k_result ${armCall}`);
|
|
@@ -33,7 +33,7 @@ function sha256(text) {
|
|
|
33
33
|
function backendFingerprint() {
|
|
34
34
|
if (cachedBackendFingerprint != null) return cachedBackendFingerprint;
|
|
35
35
|
const hash = crypto.createHash("sha256");
|
|
36
|
-
for (const relPath of ["src/llvm.mjs", "src/executable.mjs", "runtime/krt.c", "runtime/krt.h"]) {
|
|
36
|
+
for (const relPath of ["src/llvm.mjs", "src/kvm2llvm.mjs", "src/executable.mjs", "runtime/krt.c", "runtime/krt.h"]) {
|
|
37
37
|
hash.update(relPath);
|
|
38
38
|
hash.update("\0");
|
|
39
39
|
hash.update(fs.readFileSync(path.join(root, relPath)));
|
|
@@ -31,12 +31,18 @@ paths do not reclaim temporary values until the whole run ends.
|
|
|
31
31
|
|
|
32
32
|
Tail-call lowering now avoids allocating replacement input products for some
|
|
33
33
|
self-tail-recursive loops by keeping top-level product fields in Wasm locals.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
and
|
|
34
|
+
Furthermore, self-tail-recursive loops now incorporate threshold-based arena
|
|
35
|
+
compaction: when arena growth within a tail loop exceeds 1 MB, live accumulator
|
|
36
|
+
and argument graphs are compacted down to the loop entry mark using an iterative
|
|
37
|
+
staging-and-copy mechanism in `runtime.wat`, freeing dead scratch data while
|
|
38
|
+
preserving performance for hot short-lived loops.
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
Additionally, linear memory exhaustion and 32-bit unsigned offset overflow are
|
|
41
|
+
now tracked explicitly via exported flags (`oom_flag`, `oom_size`) in `runtime.wat`,
|
|
42
|
+
which the JS runtime intercepts to throw a descriptive `OutOfMemoryError` instead
|
|
43
|
+
of an untyped `unreachable` trap.
|
|
44
|
+
|
|
45
|
+
Remaining memory-management targets:
|
|
40
46
|
|
|
41
47
|
- Add allocation profiling that attributes arena growth to generated relations
|
|
42
48
|
and static allocation sites. Large regressions should be visible without
|
|
@@ -47,14 +53,6 @@ Future memory-management targets:
|
|
|
47
53
|
- Introduce safe lifetime boundaries for successful non-tail calls. The runtime
|
|
48
54
|
needs a way to reclaim scratch values after a callee result has been consumed
|
|
49
55
|
without copying large live values on every call.
|
|
50
|
-
- Make arena checkpoints part of code generation decisions, not just union
|
|
51
|
-
failure handling. Tail loops should be able to reset dead scratch space while
|
|
52
|
-
preserving the next live input/result.
|
|
53
56
|
- Keep unsigned pointer handling explicit. Wasm arena addresses are `i32`
|
|
54
57
|
offsets, and JavaScript sees exported `i32` values as signed numbers, so all
|
|
55
58
|
JS-side arena offsets must be normalized before `DataView` access.
|
|
56
|
-
|
|
57
|
-
The preferred direction is performance first: reduce allocation volume in hot
|
|
58
|
-
generated loops before adding a general garbage collector or compaction pass.
|
|
59
|
-
Copying live arena values can control memory, but it risks slowing exactly the
|
|
60
|
-
large recursive arithmetic workloads that motivated tail-call lowering.
|