@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.
@@ -2,9 +2,12 @@
2
2
  (memory (export "memory") 1)
3
3
 
4
4
  ;; Start allocation at byte offset 1024 to leave a safety margin and avoid returning 0 (nullptr)
5
- (global $arena_free (mut i32) (i32.const 1024))
5
+ (global $arena_free (export "arena_free") (mut i32) (i32.const 1024))
6
6
  ;; Maximum address of the currently allocated pages (1 page = 64KB)
7
- (global $arena_max (mut i32) (i32.const 65536))
7
+ (global $arena_max (export "arena_max") (mut i32) (i32.const 65536))
8
+ ;; Out-of-memory status flags
9
+ (global $oom_flag (export "oom_flag") (mut i32) (i32.const 0))
10
+ (global $oom_size (export "oom_size") (mut i32) (i32.const 0))
8
11
 
9
12
  ;; Bump allocator function: aligns allocation size to 8 bytes, grows memory if needed, and returns the pointer
10
13
  (func $alloc (export "alloc") (param $size i32) (result i32)
@@ -13,6 +16,18 @@
13
16
  (local $pages_needed i32)
14
17
  (local $grow_res i32)
15
18
 
19
+ ;; Check if size exceeds maximum allocatable size
20
+ local.get $size
21
+ i32.const -8
22
+ i32.gt_u
23
+ if
24
+ i32.const 1
25
+ global.set $oom_flag
26
+ local.get $size
27
+ global.set $oom_size
28
+ unreachable
29
+ end
30
+
16
31
  ;; 1. Align size to 8 bytes: size = (size + 7) & ~8
17
32
  local.get $size
18
33
  i32.const 7
@@ -31,6 +46,18 @@
31
46
  i32.add
32
47
  local.set $new_free
33
48
 
49
+ ;; Check for 32-bit unsigned overflow
50
+ local.get $new_free
51
+ local.get $old_free
52
+ i32.lt_u
53
+ if
54
+ i32.const 1
55
+ global.set $oom_flag
56
+ local.get $size
57
+ global.set $oom_size
58
+ unreachable
59
+ end
60
+
34
61
  ;; 2. Check if we exceed current memory size
35
62
  local.get $new_free
36
63
  global.get $arena_max
@@ -56,6 +83,10 @@
56
83
  i32.const -1
57
84
  i32.eq
58
85
  if
86
+ i32.const 1
87
+ global.set $oom_flag
88
+ local.get $size
89
+ global.set $oom_size
59
90
  unreachable
60
91
  end
61
92
 
@@ -99,4 +130,346 @@
99
130
  local.get $mark
100
131
  global.set $arena_free
101
132
  )
133
+
134
+ ;; Compaction staging globals
135
+ (global $compact_buf_start (mut i32) (i32.const 0))
136
+ (global $compact_buf_free (mut i32) (i32.const 0))
137
+
138
+ ;; Ensure memory capacity in staging buffer
139
+ (func $ensure_buf_capacity (param $size i32)
140
+ (local $needed i32)
141
+ (local $pages i32)
142
+ (local $grow_res i32)
143
+
144
+ global.get $compact_buf_free
145
+ local.get $size
146
+ i32.add
147
+ local.set $needed
148
+
149
+ ;; Check for 32-bit overflow
150
+ local.get $needed
151
+ global.get $compact_buf_free
152
+ i32.lt_u
153
+ if
154
+ i32.const 1
155
+ global.set $oom_flag
156
+ local.get $size
157
+ global.set $oom_size
158
+ unreachable
159
+ end
160
+
161
+ local.get $needed
162
+ global.get $arena_max
163
+ i32.gt_u
164
+ if
165
+ local.get $needed
166
+ global.get $arena_max
167
+ i32.sub
168
+ i32.const 65535
169
+ i32.add
170
+ i32.const 65536
171
+ i32.div_u
172
+ local.set $pages
173
+
174
+ local.get $pages
175
+ memory.grow
176
+ local.set $grow_res
177
+
178
+ local.get $grow_res
179
+ i32.const -1
180
+ i32.eq
181
+ if
182
+ i32.const 1
183
+ global.set $oom_flag
184
+ local.get $size
185
+ global.set $oom_size
186
+ unreachable
187
+ end
188
+
189
+ global.get $arena_max
190
+ local.get $pages
191
+ i32.const 65536
192
+ i32.mul
193
+ i32.add
194
+ global.set $arena_max
195
+ end
196
+ )
197
+
198
+ ;; Initialize compaction staging buffer at current arena top
199
+ (func $compact_start (export "compact_start")
200
+ global.get $arena_free
201
+ global.set $compact_buf_start
202
+ global.get $arena_free
203
+ global.set $compact_buf_free
204
+ )
205
+
206
+ ;; Compact an object graph reachable from $root down toward $mark
207
+ (func $compact_obj (export "compact_obj") (param $root i32) (param $mark i32) (result i32)
208
+ (local $curr i32)
209
+ (local $first_final i32)
210
+ (local $prev_dest i32)
211
+ (local $dest i32)
212
+ (local $final_ptr i32)
213
+ (local $tag i32)
214
+ (local $payload i32)
215
+ (local $size i32)
216
+ (local $alignedSize i32)
217
+ (local $N i32)
218
+ (local $i i32)
219
+ (local $offsetVal i32)
220
+ (local $fieldPtr i32)
221
+ (local $child_final i32)
222
+
223
+ local.get $root
224
+ local.set $curr
225
+ i32.const 0
226
+ local.set $prev_dest
227
+
228
+ (block $break_chain
229
+ (loop $chain_loop
230
+ ;; 1. Check null / empty
231
+ local.get $curr
232
+ i32.eqz
233
+ if
234
+ local.get $prev_dest
235
+ if
236
+ local.get $prev_dest
237
+ i32.const 0
238
+ i32.store offset=8
239
+ end
240
+ br $break_chain
241
+ end
242
+
243
+ ;; 2. Check if < mark
244
+ local.get $curr
245
+ local.get $mark
246
+ i32.lt_u
247
+ if
248
+ local.get $prev_dest
249
+ if
250
+ local.get $prev_dest
251
+ local.get $curr
252
+ i32.store offset=8
253
+ end
254
+ local.get $first_final
255
+ i32.eqz
256
+ if
257
+ local.get $curr
258
+ local.set $first_final
259
+ end
260
+ br $break_chain
261
+ end
262
+
263
+ ;; 3. Check if forwarded
264
+ local.get $curr
265
+ i32.load offset=0
266
+ local.set $size
267
+
268
+ local.get $size
269
+ i32.const -1
270
+ i32.eq
271
+ if
272
+ local.get $curr
273
+ i32.load offset=4
274
+ local.set $final_ptr
275
+ local.get $prev_dest
276
+ if
277
+ local.get $prev_dest
278
+ local.get $final_ptr
279
+ i32.store offset=8
280
+ end
281
+ local.get $first_final
282
+ i32.eqz
283
+ if
284
+ local.get $final_ptr
285
+ local.set $first_final
286
+ end
287
+ br $break_chain
288
+ end
289
+
290
+ ;; 4. Compute final_ptr = mark + (compact_buf_free - compact_buf_start)
291
+ local.get $mark
292
+ global.get $compact_buf_free
293
+ global.get $compact_buf_start
294
+ i32.sub
295
+ i32.add
296
+ local.set $final_ptr
297
+
298
+ local.get $first_final
299
+ i32.eqz
300
+ if
301
+ local.get $final_ptr
302
+ local.set $first_final
303
+ end
304
+
305
+ ;; If prev_dest != 0, patch its payload to final_ptr
306
+ local.get $prev_dest
307
+ if
308
+ local.get $prev_dest
309
+ local.get $final_ptr
310
+ i32.store offset=8
311
+ end
312
+
313
+ ;; 5. Check if Variant (size == 12)
314
+ local.get $size
315
+ i32.const 12
316
+ i32.eq
317
+ if
318
+ i32.const 16
319
+ call $ensure_buf_capacity
320
+
321
+ local.get $curr
322
+ i32.load offset=4
323
+ local.set $tag
324
+ local.get $curr
325
+ i32.load offset=8
326
+ local.set $payload
327
+
328
+ local.get $curr
329
+ i32.const -1
330
+ i32.store offset=0
331
+ local.get $curr
332
+ local.get $final_ptr
333
+ i32.store offset=4
334
+
335
+ global.get $compact_buf_free
336
+ local.set $dest
337
+ global.get $compact_buf_free
338
+ i32.const 16
339
+ i32.add
340
+ global.set $compact_buf_free
341
+
342
+ local.get $dest
343
+ i32.const 12
344
+ i32.store offset=0
345
+ local.get $dest
346
+ local.get $tag
347
+ i32.store offset=4
348
+
349
+ local.get $dest
350
+ local.set $prev_dest
351
+ local.get $payload
352
+ local.set $curr
353
+ br $chain_loop
354
+ end
355
+
356
+ ;; 6. Product handling (size != 12)
357
+ local.get $size
358
+ i32.const 7
359
+ i32.add
360
+ i32.const -8
361
+ i32.and
362
+ local.set $alignedSize
363
+
364
+ local.get $alignedSize
365
+ call $ensure_buf_capacity
366
+
367
+ local.get $curr
368
+ i32.load offset=4
369
+ local.set $N
370
+
371
+ local.get $curr
372
+ i32.const -1
373
+ i32.store offset=0
374
+ local.get $curr
375
+ local.get $final_ptr
376
+ i32.store offset=4
377
+
378
+ global.get $compact_buf_free
379
+ local.set $dest
380
+ global.get $compact_buf_free
381
+ local.get $alignedSize
382
+ i32.add
383
+ global.set $compact_buf_free
384
+
385
+ local.get $dest
386
+ local.get $size
387
+ i32.store offset=0
388
+ local.get $dest
389
+ local.get $N
390
+ i32.store offset=4
391
+
392
+ i32.const 0
393
+ local.set $i
394
+ (block $break_prod
395
+ (loop $loop_prod
396
+ local.get $i
397
+ local.get $N
398
+ i32.ge_u
399
+ br_if $break_prod
400
+
401
+ local.get $curr
402
+ i32.const 8
403
+ local.get $i
404
+ i32.const 4
405
+ i32.mul
406
+ i32.add
407
+ i32.add
408
+ i32.load
409
+ local.set $offsetVal
410
+
411
+ local.get $dest
412
+ i32.const 8
413
+ local.get $i
414
+ i32.const 4
415
+ i32.mul
416
+ i32.add
417
+ i32.add
418
+ local.get $offsetVal
419
+ i32.store
420
+
421
+ local.get $curr
422
+ local.get $offsetVal
423
+ i32.add
424
+ i32.load
425
+ local.set $fieldPtr
426
+
427
+ local.get $fieldPtr
428
+ local.get $mark
429
+ call $compact_obj
430
+ local.set $child_final
431
+
432
+ local.get $dest
433
+ local.get $offsetVal
434
+ i32.add
435
+ local.get $child_final
436
+ i32.store
437
+
438
+ local.get $i
439
+ i32.const 1
440
+ i32.add
441
+ local.set $i
442
+ br $loop_prod
443
+ )
444
+ )
445
+ br $break_chain
446
+ )
447
+ )
448
+
449
+ local.get $first_final
450
+ )
451
+
452
+ ;; Finalize compaction by copying staging buffer into mark and resetting arena_free
453
+ (func $compact_finish (export "compact_finish") (param $mark i32)
454
+ (local $total_size i32)
455
+ global.get $compact_buf_free
456
+ global.get $compact_buf_start
457
+ i32.sub
458
+ local.set $total_size
459
+
460
+ local.get $total_size
461
+ i32.const 0
462
+ i32.gt_u
463
+ if
464
+ local.get $mark
465
+ global.get $compact_buf_start
466
+ local.get $total_size
467
+ memory.copy
468
+ end
469
+
470
+ local.get $mark
471
+ local.get $total_size
472
+ i32.add
473
+ global.set $arena_free
474
+ )
102
475
  )
@@ -159,6 +159,7 @@ export function lowerToWasm(relDef, name, options = {}) {
159
159
  for (const field of inputProductFields) {
160
160
  registers.add(field.local);
161
161
  }
162
+ registers.add("tail_loop_mark");
162
163
 
163
164
  let nextUnionId = 0;
164
165
  const tailLoopLabel = "$tail_loop";
@@ -379,6 +380,24 @@ export function lowerToWasm(relDef, name, options = {}) {
379
380
  );
380
381
  lines.push(branchWat);
381
382
  }
383
+ lines.push(` ;; compact if arena growth exceeds 1MB`);
384
+ lines.push(` global.get $arena_free`);
385
+ lines.push(` local.get $tail_loop_mark`);
386
+ lines.push(` i32.sub`);
387
+ lines.push(` i32.const 1048576`);
388
+ lines.push(` i32.gt_u`);
389
+ lines.push(` if`);
390
+ lines.push(` call $compact_start`);
391
+ for (let i = 0; i < N; i++) {
392
+ const fieldTmp = `${dest}_f${i}`;
393
+ lines.push(` local.get $${fieldTmp}`);
394
+ lines.push(` local.get $tail_loop_mark`);
395
+ lines.push(` call $compact_obj`);
396
+ lines.push(` local.set $${fieldTmp}`);
397
+ }
398
+ lines.push(` local.get $tail_loop_mark`);
399
+ lines.push(` call $compact_finish`);
400
+ lines.push(` end`);
382
401
  for (let i = 0; i < N; i++) {
383
402
  const branch = sortedBranches[i];
384
403
  const fieldTmp = `${dest}_f${i}`;
@@ -565,6 +584,21 @@ export function lowerToWasm(relDef, name, options = {}) {
565
584
  registers.add(callSrc);
566
585
  materializeInputProduct(lines, callSrc);
567
586
  }
587
+ lines.push(` ;; compact if arena growth exceeds 1MB`);
588
+ lines.push(` global.get $arena_free`);
589
+ lines.push(` local.get $tail_loop_mark`);
590
+ lines.push(` i32.sub`);
591
+ lines.push(` i32.const 1048576`);
592
+ lines.push(` i32.gt_u`);
593
+ lines.push(` if`);
594
+ lines.push(` call $compact_start`);
595
+ lines.push(` local.get $${callSrc}`);
596
+ lines.push(` local.get $tail_loop_mark`);
597
+ lines.push(` call $compact_obj`);
598
+ lines.push(` local.set $${callSrc}`);
599
+ lines.push(` local.get $tail_loop_mark`);
600
+ lines.push(` call $compact_finish`);
601
+ lines.push(` end`);
568
602
  lines.push(` local.get $${callSrc}`);
569
603
  lines.push(` local.set $in`);
570
604
  lines.push(initInputProductLocals());
@@ -641,6 +675,8 @@ export function lowerToWasm(relDef, name, options = {}) {
641
675
  const wat = `(func $${name} (export "${name}") (param $in i32) (result i32 i32)
642
676
  ${localDecls}
643
677
  ${inputProductLocalInit}
678
+ global.get $arena_free
679
+ local.set $tail_loop_mark
644
680
  (loop ${tailLoopLabel}
645
681
  ${profWat}${bodyWat}
646
682
  )
@@ -31,6 +31,24 @@ const METADATA_SECTION = "k.metadata";
31
31
  const ARTIFACT_FORMAT = "k-wasm";
32
32
  const ARTIFACT_VERSION = 1;
33
33
 
34
+ class OutOfMemoryError extends Error {
35
+ constructor(message) {
36
+ super(message);
37
+ this.name = "OutOfMemoryError";
38
+ }
39
+ }
40
+
41
+ function checkWasmOom(exports) {
42
+ if (exports.oom_flag && exports.oom_flag.value) {
43
+ const size = exports.oom_size ? exports.oom_size.value : 0;
44
+ const max = exports.arena_max ? exports.arena_max.value : 0;
45
+ const free = exports.arena_free ? exports.arena_free.value : 0;
46
+ throw new OutOfMemoryError(
47
+ `WebAssembly Out of Memory: linear memory arena exhausted (failed to allocate ${size} bytes; arena used: ${free >>> 0} bytes, arena max: ${max >>> 0} bytes)`
48
+ );
49
+ }
50
+ }
51
+
34
52
  const cleanName = (name) => "rel_" + name.replace(/[^a-zA-Z0-9_]/g, "_");
35
53
 
36
54
  async function getWabt() {
@@ -894,9 +912,21 @@ async function instantiateWasmArtifact(wasmBuffer) {
894
912
  const t1 = process.hrtime.bigint();
895
913
  const mark = exports.arena_mark ? exports.arena_mark() : 0;
896
914
  const arenaValues = new Map();
897
- const ptrIn = writeValueToArena(exports, value, inputPattern, 0, arenaValues, tags, metadata.inputPattern);
915
+ let ptrIn;
916
+ try {
917
+ ptrIn = writeValueToArena(exports, value, inputPattern, 0, arenaValues, tags, metadata.inputPattern);
918
+ } catch (error) {
919
+ checkWasmOom(exports);
920
+ throw error;
921
+ }
898
922
  const t2 = process.hrtime.bigint();
899
- const result = exports[metadata.entry](ptrIn);
923
+ let result;
924
+ try {
925
+ result = exports[metadata.entry](ptrIn);
926
+ } catch (error) {
927
+ checkWasmOom(exports);
928
+ throw error;
929
+ }
900
930
  if (result[1] !== 1) {
901
931
  if (exports.arena_reset) exports.arena_reset(mark);
902
932
  throw new Error("Wasm relation execution failed (returned false)");
@@ -933,9 +963,21 @@ async function instantiateWasmArtifact(wasmBuffer) {
933
963
  const t1 = process.hrtime.bigint();
934
964
  const mark = exports.arena_mark ? exports.arena_mark() : 0;
935
965
  const arenaValues = new Map();
936
- const ptrIn = writeValueToArena(exports, inputValue, inputPattern, 0, arenaValues, tags, metadata.inputPattern);
966
+ let ptrIn;
967
+ try {
968
+ ptrIn = writeValueToArena(exports, inputValue, inputPattern, 0, arenaValues, tags, metadata.inputPattern);
969
+ } catch (error) {
970
+ checkWasmOom(exports);
971
+ throw error;
972
+ }
937
973
  const t2 = process.hrtime.bigint();
938
- const result = exports[metadata.entry](ptrIn);
974
+ let result;
975
+ try {
976
+ result = exports[metadata.entry](ptrIn);
977
+ } catch (error) {
978
+ checkWasmOom(exports);
979
+ throw error;
980
+ }
939
981
  if (result[1] !== 1) {
940
982
  if (exports.arena_reset) exports.arena_reset(mark);
941
983
  return undefined;
@@ -982,6 +1024,7 @@ export {
982
1024
  ARTIFACT_FORMAT,
983
1025
  ARTIFACT_VERSION,
984
1026
  METADATA_SECTION,
1027
+ OutOfMemoryError,
985
1028
  appendCustomSection,
986
1029
  compileWasmArtifactFromKVM,
987
1030
  compileWasmArtifactFromObject,
@@ -998,6 +1041,7 @@ export default {
998
1041
  ARTIFACT_FORMAT,
999
1042
  ARTIFACT_VERSION,
1000
1043
  METADATA_SECTION,
1044
+ OutOfMemoryError,
1001
1045
  appendCustomSection,
1002
1046
  compileWasmArtifactFromKVM,
1003
1047
  compileWasmArtifactFromObject,
@@ -88,6 +88,14 @@ console.log("==> Running runtime.wat Core Allocator Tests");
88
88
  const ptr3 = runtimeExports.alloc(70000);
89
89
  assert.ok(ptr3 > 1040, "Large allocation must return a valid pointer");
90
90
  assert.equal(ptr3 % 8, 0, "Large allocation must be 8-byte aligned");
91
+
92
+ // Test 5: Out of Memory flag recording on overflow
93
+ assert.equal(runtimeExports.oom_flag.value, 0, "OOM flag must be 0 initially");
94
+ assert.throws(() => {
95
+ runtimeExports.alloc(-1);
96
+ }, /unreachable/);
97
+ assert.equal(runtimeExports.oom_flag.value, 1, "OOM flag must be 1 after allocation failure");
98
+ assert.equal(runtimeExports.oom_size.value, -1, "OOM size must record requested size");
91
99
  console.log("Core Allocator tests passed successfully!");
92
100
  }
93
101
 
@@ -20,6 +20,7 @@ import {
20
20
  import { Value, isProduct, isVariant } from "../Value.mjs";
21
21
  import { encodeLibrary } from "../object.mjs";
22
22
  import { setVfsFile, getVfsFile, getAllVfsFiles } from "./shims/fs.mjs";
23
+ import { createOutputEntryElement } from "./tree-view.mjs";
23
24
 
24
25
  // Make Value and helpers globally available for custom codecs
25
26
  if (typeof window !== "undefined") {
@@ -155,7 +156,7 @@ function setStatus(busy, text = "Ready") {
155
156
  }
156
157
  }
157
158
 
158
- function appendEntry({ prompt, command, outputs = [], errors = [], warnings = [], duration = null, timing = null }) {
159
+ function appendEntry({ prompt, command, outputs = [], errors = [], warnings = [], duration = null, timing = null, value = null }) {
159
160
  if (!outputEl) return;
160
161
 
161
162
  const entry = document.createElement("div");
@@ -220,11 +221,26 @@ function appendEntry({ prompt, command, outputs = [], errors = [], warnings = []
220
221
  }
221
222
 
222
223
  // Outputs
223
- for (const out of outputs) {
224
- const outDiv = document.createElement("div");
225
- outDiv.className = "entry-line line-output";
226
- outDiv.innerHTML = ansiToHtml(out);
227
- entry.appendChild(outDiv);
224
+ if (value && outputs.length > 0) {
225
+ const rawText = outputs.join("\n");
226
+ const allLines = outputs[0].split("\n");
227
+ const codecOutputs = allLines.slice(1);
228
+ const timingText = outputs.length > 1 ? outputs[1] : null;
229
+
230
+ const outElement = createOutputEntryElement({
231
+ value,
232
+ rawText,
233
+ codecOutputs,
234
+ timingText
235
+ });
236
+ entry.appendChild(outElement);
237
+ } else {
238
+ for (const out of outputs) {
239
+ const outDiv = document.createElement("div");
240
+ outDiv.className = "entry-line line-output";
241
+ outDiv.innerHTML = ansiToHtml(out);
242
+ entry.appendChild(outDiv);
243
+ }
228
244
  }
229
245
 
230
246
  outputEl.appendChild(entry);
@@ -298,7 +314,8 @@ export async function executeCommand(input) {
298
314
  errors,
299
315
  warnings,
300
316
  duration,
301
- timing: state.lastTiming
317
+ timing: state.lastTiming,
318
+ value: state.lastResult
302
319
  });
303
320
 
304
321
  // Re-create completer with updated state