@fraczak/k 6.4.0 → 6.5.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 CHANGED
@@ -7,6 +7,23 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [6.5.0] — 2026-09-22 — Tail-loop arena compaction & OOM diagnostics
11
+
12
+ ### WebAssembly Backend
13
+
14
+ - Added threshold-based arena compaction in self-tail-recursive loops to reclaim dead scratch space, allowing deep computations (e.g. multi-thousand-bit arithmetic) without exhausting linear memory.
15
+ - Added explicit Out-Of-Memory error reporting (`OutOfMemoryError`), tracking allocation failure and 32-bit offset overflow via exported runtime globals.
16
+
17
+ ### Runtime & REPL
18
+
19
+ - Replaced recursive traversal in `codecs/runtime/show-value.mjs` and `Value.mjs` with iterative loops, eliminating `Maximum call stack size exceeded` errors when rendering deep variant chains.
20
+
21
+ ### Examples
22
+
23
+ - Converted arithmetic helper functions in `Examples/arithmetics.k` to tail-recursive relations.
24
+
25
+ ---
26
+
10
27
  ## [6.2.3] — 2026-06-03 — Unified k compiler output
11
28
 
12
29
  ### Toolchain
@@ -39,22 +39,33 @@ empty? = {empty /yes if, () then} .then;
39
39
  concat = ${bits x, bits y} {.x inv x, .y y} concat_;
40
40
  concat_ = ${bits x, bits y}
41
41
  <
42
- {.x /0 x, .y |0 y} concat_,
43
- {.x /1 x, .y |1 y} concat_,
44
- .y
42
+ {.x empty? if, .y then} .then,
43
+ <
44
+ {.x /0 x, .y |0 y},
45
+ {.x /1 x, .y |1 y}
46
+ > concat_
45
47
  >;
46
48
  inv = {()x, 0b r} _inv;
47
- _inv = ${bits x, bits r} < {.x /0 x, .r |0 r} _inv, {.x /1 x, .r |1 r} _inv, .r >;
49
+ _inv = ${bits x, bits r}
50
+ <
51
+ {.x empty? if, .r then} .then,
52
+ <
53
+ {.x /0 x, .r |0 r},
54
+ {.x /1 x, .r |1 r}
55
+ > _inv
56
+ >;
48
57
 
49
58
  inOrder = ${bits x, bits y} {.x inv x, .y inv y, yes inOrder} inOrder_;
50
59
  inOrder_ = ${bits x, bits y, yes_no inOrder}
51
60
  <
52
- {.x /0 x, .y /0 y, .inOrder inOrder} inOrder_,
53
- {.x /1 x, .y /1 y, .inOrder inOrder} inOrder_,
54
- {.x /0 x, .y /1 y, .inOrder inOrder} inOrder_,
55
- {.x /1 y, .y /0 x, .inOrder swap_yes_no inOrder} inOrder_,
56
- {.x </0,/1> if, .inOrder swap_yes_no then} .then,
57
- .inOrder
61
+ {.x </0,/1> if1, .y /_ if2, .inOrder swap_yes_no then} .then,
62
+ {.x /_ if, .inOrder then} .then,
63
+ <
64
+ {.x /0 x, .y /0 y, .inOrder inOrder},
65
+ {.x /1 x, .y /1 y, .inOrder inOrder},
66
+ {.x /0 x, .y /1 y, .inOrder inOrder},
67
+ {.x /1 y, .y /0 x, .inOrder swap_yes_no inOrder}
68
+ > inOrder_
58
69
  >;
59
70
 
60
71
  GT = {inOrder /no if, () then} . then;
@@ -63,18 +74,21 @@ EQ = GT {.x y, .y x} GT;
63
74
 
64
75
  toBitList = {inv x, nil r} _toBitList;
65
76
  _toBitList = <
66
- {.x empty? if, .r then }.then,
67
- {.x /0 x, {0 car, .r cdr} cons r} _toBitList,
68
- {.x /1 x, {1 car, .r cdr} cons r} _toBitList
77
+ {.x empty? if, .r then}.then,
78
+ <
79
+ {.x /0 x, {0 car, .r cdr} cons r},
80
+ {.x /1 x, {1 car, .r cdr} cons r}
81
+ > _toBitList
69
82
  >;
70
- fromBitList =
71
- <
72
- cdr <
73
- {car /0 if, cdr fromBitList |0 then} .then,
74
- {car /1 if, cdr fromBitList |1 then} .then
75
- >,
76
- 0b
77
- > $bits;
83
+ fromBitList = {() l, 0b r} _fromBitList inv $bits;
84
+ _fromBitList =
85
+ <
86
+ {.l nil? if, .r then} .then,
87
+ <
88
+ {.l car /0 if, {.l cdr l, .r |0 r} then} .then,
89
+ {.l car /1 if, {.l cdr l, .r |1 r} then} .then
90
+ > _fromBitList
91
+ >;
78
92
 
79
93
  --------------------------------------
80
94
  $ int = < bits '+', bits '-' >;
@@ -99,9 +113,9 @@ inc = $int <
99
113
  > $int;
100
114
 
101
115
  _inc = ${bits x, bits y} <
116
+ {.x empty? if, .y |1 then} .then,
102
117
  {.x /0 x, .y |1 y} concat_,
103
- {.x /1 x, .y |0 y} _inc,
104
- .y |1
118
+ {.x /1 x, .y |0 y} _inc
105
119
  >;
106
120
 
107
121
  dec = $int <
@@ -149,38 +163,44 @@ _plus_ =
149
163
  {.x empty? if, .y empty? and, {.r x, 1 r} _inv then} .then,
150
164
  {.x empty? if, .c/0 and, {.r x, .y r} _inv then} .then,
151
165
 
152
- {.x y, .y empty? x, .c c, .r r} _plus_,
153
- {<.c/0 0, 1> x,
154
- .y y, .x empty? c0 c, .r r} _plus_,
166
+ <
167
+ {.x y, .y empty? x, .c c, .r r},
168
+ {<.c/0 0, 1> x, .y y, .x empty? c0 c, .r r},
155
169
 
156
- {.x /0 x, .y /0 y, .c/0 c0 c, .r |0 r} _plus_,
157
- {.x /0 x, .y /0 y, .c/1 c0 c, .r |1 r} _plus_,
170
+ {.x /0 x, .y /0 y, .c/0 c0 c, .r |0 r},
171
+ {.x /0 x, .y /0 y, .c/1 c0 c, .r |1 r},
158
172
 
159
- {.x /0 x, .y /1 y, .c/0 c0 c, .r |1 r} _plus_,
160
- {.x /0 x, .y /1 y, .c/1 c1 c, .r |0 r} _plus_,
161
- {.x /1 x, .y /0 y, .c/0 c0 c, .r |1 r} _plus_,
162
- {.x /1 x, .y /0 y, .c/1 c1 c, .r |0 r} _plus_,
173
+ {.x /0 x, .y /1 y, .c/0 c0 c, .r |1 r},
174
+ {.x /0 x, .y /1 y, .c/1 c1 c, .r |0 r},
175
+ {.x /1 x, .y /0 y, .c/0 c0 c, .r |1 r},
176
+ {.x /1 x, .y /0 y, .c/1 c1 c, .r |0 r},
163
177
 
164
- {.x /1 x, .y /1 y, .c/0 c1 c, .r |0 r} _plus_,
165
- {.x /1 x, .y /1 y, .c/1 c1 c, .r |1 r} _plus_
178
+ {.x /1 x, .y /1 y, .c/0 c1 c, .r |0 r},
179
+ {.x /1 x, .y /1 y, .c/1 c1 c, .r |1 r}
180
+ > _plus_
166
181
  >
167
182
  ;
168
183
 
169
- _minus =
170
- -- least significat bit first substraction with carry
171
- ${bits x, bits y, carry c}
184
+ _minus = {.x x, .y y, .c c, 0b r} _minus_;
185
+
186
+ _minus_ =
187
+ -- least significant bit first substraction with carry
188
+ ${bits x, bits y, carry c, bits r}
172
189
  <
173
- {.x then, .y empty? if, .c/0 and} .then,
174
- {.x/1 then, .y empty? if, .c/1 and} .then |0,
175
- {.x/0 x, .y empty? y, .c/1 c1 c} _minus |1,
176
- {.x/0 x, .y/0 y, .c/0 c0 c} _minus |0,
177
- {.x/0 x, .y/0 y, .c/1 c1 c} _minus |1,
178
- {.x/0 x, .y/1 y, .c/0 c1 c} _minus |1,
179
- {.x/0 x, .y/1 y, .c/1 c1 c} _minus |0,
180
- {.x/1 x, .y/0 y, .c/0 c0 c} _minus |1,
181
- {.x/1 x, .y/0 y, .c/1 c0 c} _minus |0,
182
- {.x/1 x, .y/1 y, .c/0 c0 c} _minus |0,
183
- {.x/1 x, .y/1 y, .c/1 c1 c} _minus |1
190
+ {.y empty? if, .c/0 and, {.r x, .x r} _inv then} .then,
191
+ <
192
+ {.x/0 x, .y empty? y, .c/1 c1 c, .r |1 r},
193
+ {.x/1 x, .y empty? y, .c/1 c0 c, .r |0 r},
194
+
195
+ {.x/0 x, .y/0 y, .c/0 c0 c, .r |0 r},
196
+ {.x/0 x, .y/0 y, .c/1 c1 c, .r |1 r},
197
+ {.x/0 x, .y/1 y, .c/0 c1 c, .r |1 r},
198
+ {.x/0 x, .y/1 y, .c/1 c1 c, .r |0 r},
199
+ {.x/1 x, .y/0 y, .c/0 c0 c, .r |1 r},
200
+ {.x/1 x, .y/0 y, .c/1 c0 c, .r |0 r},
201
+ {.x/1 x, .y/1 y, .c/0 c0 c, .r |0 r},
202
+ {.x/1 x, .y/1 y, .c/1 c1 c, .r |1 r}
203
+ > _minus_
184
204
  >
185
205
  ;
186
206
 
@@ -212,16 +232,18 @@ _times =
212
232
  ${bits result, bits _x, bits y}
213
233
  <
214
234
  { .y empty? if, .result then } .then,
215
- {
216
- .result |0 result,
217
- ._x _x,
218
- .y /0 y
219
- } _times,
220
- {
221
- {._x x, .result |0 y, c0 c } _plus result,
222
- ._x _x,
223
- .y /1 y
224
- } _times
235
+ <
236
+ {
237
+ .result |0 result,
238
+ ._x _x,
239
+ .y /0 y
240
+ },
241
+ {
242
+ {._x x, .result |0 y, c0 c } _plus result,
243
+ ._x _x,
244
+ .y /1 y
245
+ }
246
+ > _times
225
247
  >
226
248
  ;
227
249
 
@@ -327,17 +349,18 @@ lcm =
327
349
  {{.x int x, .y int y} times x, gcd int y} div .div abs
328
350
  ;
329
351
 
330
- factorial = <
331
- zero_int? 1 int,
332
- /"+" int {dec factorial x, () y} times
352
+ factorial = {()n, 1 int acc} factorial_;
353
+ factorial_ = <
354
+ {.n zero_int? if, .acc then} .then,
355
+ {.n pos? dec n, {.n x, .acc y} times acc} factorial_
333
356
  >;
334
357
 
335
358
  fib_ =
336
359
  <
360
+ {.n int zero_int? if, ."fib(n),fib(n.1)" then} .then,
337
361
  { .n int dec /"+" n,
338
362
  ."fib(n),fib(n.1)" {plus x, .x y} "fib(n),fib(n.1)"
339
- } fib_,
340
- ."fib(n),fib(n.1)"
363
+ } fib_
341
364
  >
342
365
  ;
343
366
 
package/Value.mjs CHANGED
@@ -176,8 +176,14 @@ class Value {
176
176
 
177
177
  toString() {
178
178
  if (isVariant(this)) {
179
- // return `{${JSON.stringify(this.tag)}:${this.value.toString()}}`;
180
- return `${this.value.toString()}|${pLabel(this.tag)}`;
179
+ const tags = [];
180
+ let curr = this;
181
+ while (isVariant(curr)) {
182
+ tags.push(pLabel(curr.tag));
183
+ curr = curr.value;
184
+ }
185
+ const base = curr ? curr.toString() : "";
186
+ return tags.length === 0 ? base : `${base}|${tags.reverse().join("|")}`;
181
187
  }
182
188
  if (isProduct(this)) {
183
189
  return `{${Object.entries(this.product).map(([k, v]) => `${JSON.stringify(k)}:${v.toString()}`).join(',')}}`;
@@ -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
- That improves arithmetic workloads such as `factorial:o`, but it is still only
35
- a first step. Large runs still allocate far more arena memory than the final
36
- result requires because intermediate relation results, accumulator fragments,
37
- and temporary products remain in the bump arena.
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
- Future memory-management targets:
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.
@@ -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
  )