@onda-lang/wasm-compiler 0.7.4 → 0.8.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.
@@ -42,7 +42,23 @@ const MAX_MEMORY_PAGES = 65_536;
42
42
  const WASM32_ADDRESS_SPACE_BYTES = MAX_MEMORY_PAGES * PAGE_BYTES;
43
43
  const DEFAULT_OPTIMIZE_LEVEL = 4;
44
44
  const ONDA_PROCESS_FULL_BLOCK = (1 << 0) | (1 << 1);
45
+ const DELEGATE_RECORD_HEADER_SIZE = 12;
46
+ const DELEGATE_BATCH_STORAGE_OFFSET = 0;
47
+ const DELEGATE_BATCH_CAPACITY_OFFSET = 4;
48
+ const DELEGATE_BATCH_USED_OFFSET = 8;
49
+ const DELEGATE_BATCH_RECORD_COUNT_OFFSET = 12;
50
+ const DELEGATE_BATCH_OVERFLOW_OFFSET = 16;
51
+ const PRINT_RECORD_HEADER_SIZE = 12;
52
+ const PRINT_BATCH_STORAGE_OFFSET = 0;
53
+ const PRINT_BATCH_CAPACITY_OFFSET = 4;
54
+ const PRINT_BATCH_USED_OFFSET = 8;
55
+ const PRINT_BATCH_RECORD_COUNT_OFFSET = 12;
56
+ const PRINT_BATCH_OVERFLOW_OFFSET = 16;
57
+ const EXECUTION_OUTPUT_DELEGATE_BATCH_OFFSET = 0;
58
+ const EXECUTION_OUTPUT_PRINT_BATCH_OFFSET = 4;
59
+ const EXECUTION_OUTPUT_SEQUENCE_OFFSET = 8;
45
60
  const RUNTIME_FAILURE_GLOBAL = "$onda.runtime_failure";
61
+ const INIT_ALL_GLOBAL = "$onda.init_all";
46
62
  const MATH_KERNEL_INTRINSICS = new Set([
47
63
  "sin",
48
64
  "cos",
@@ -63,6 +79,9 @@ const POINTER_GLOBALS = Object.freeze({
63
79
  params: "$onda.params",
64
80
  state: "$onda.state",
65
81
  eventPayload: "$onda.event_payload",
82
+ delegateBatch: "$onda.delegate_batch",
83
+ printBatch: "$onda.print_batch",
84
+ outputSequence: "$onda.output_sequence",
66
85
  buffers: "$onda.buffers",
67
86
  bufferWrites: "$onda.buffer_writes",
68
87
  bufferFrames: "$onda.buffer_frames",
@@ -819,6 +838,7 @@ class MirCompiler {
819
838
  "params",
820
839
  "buffers",
821
840
  "events",
841
+ "delegates",
822
842
  ]) {
823
843
  if (!Array.isArray(mir.interface[field])) {
824
844
  this.fail(`MIR interface field '${field}' must be an array`);
@@ -983,6 +1003,12 @@ class MirCompiler {
983
1003
  "control_mirror",
984
1004
  ]);
985
1005
  for (const [stateId, slot] of this.mir.state.entries()) {
1006
+ if (typeof slot?.authored !== "boolean") {
1007
+ this.fail(`state slot ${stateId} has an invalid authored flag`);
1008
+ }
1009
+ if (slot?.pinned !== undefined && typeof slot.pinned !== "boolean") {
1010
+ this.fail(`state slot ${stateId} has an invalid pinned flag`);
1011
+ }
986
1012
  if (!persistenceKinds.has(slot?.persistence)) {
987
1013
  this.fail(
988
1014
  `state slot ${stateId} has invalid persistence '${String(slot?.persistence)}'`,
@@ -1022,7 +1048,8 @@ class MirCompiler {
1022
1048
  if (
1023
1049
  !func?.attributes ||
1024
1050
  !origins.has(func.attributes.origin) ||
1025
- !inlineHints.has(func.attributes.inline)
1051
+ !inlineHints.has(func.attributes.inline) ||
1052
+ typeof func.attributes.runtime_context !== "boolean"
1026
1053
  ) {
1027
1054
  this.fail(
1028
1055
  `function ${functionId} has invalid schema-${SUPPORTED_MIR_SCHEMA_VERSION} attributes`,
@@ -1550,10 +1577,15 @@ class MirCompiler {
1550
1577
  if (value.kind === "call" && Number.isInteger(value.data?.function)) {
1551
1578
  callees[functionId].add(value.data.function);
1552
1579
  }
1580
+ const fixedDelegatePayloadMayFail = value.kind === "publish_delegate"
1581
+ && this.mir.interface.delegates[value.data?.delegate]?.params.some(
1582
+ (param) => this.type(param.ty).kind === "array",
1583
+ );
1553
1584
  const bounds = value.data?.bounds;
1554
1585
  if (
1555
1586
  value.kind === "process_frame"
1556
1587
  || value.kind === "slice_copy"
1588
+ || fixedDelegatePayloadMayFail
1557
1589
  || binaryMayFail(functionId, value)
1558
1590
  || (checkedBoundsKinds.has(value.kind) && bounds === "checked")
1559
1591
  || (dynamicBoundsKinds.has(value.kind) && bounds !== "unchecked")
@@ -1590,6 +1622,9 @@ class MirCompiler {
1590
1622
  this.eventLayout = this.mir.interface.events.map((event) =>
1591
1623
  this.layoutEventValues(event.params),
1592
1624
  );
1625
+ this.delegateLayout = this.mir.interface.delegates.map((delegate) =>
1626
+ this.layoutEventValues(delegate.params),
1627
+ );
1593
1628
  this.requireWasm32Extent(
1594
1629
  this.stateLayout.byteLength,
1595
1630
  "MIR physical state storage",
@@ -1604,6 +1639,12 @@ class MirCompiler {
1604
1639
  `MIR event ${eventId} fixed payload storage`,
1605
1640
  );
1606
1641
  }
1642
+ for (const [delegateId, layout] of this.delegateLayout.entries()) {
1643
+ this.requireWasm32Extent(
1644
+ layout.minimumByteLength,
1645
+ `MIR delegate ${delegateId} fixed payload storage`,
1646
+ );
1647
+ }
1607
1648
 
1608
1649
  for (let id = 0; id < this.mir.const_data.length; id += 1) {
1609
1650
  const data = this.mir.const_data[id];
@@ -1818,6 +1859,12 @@ class MirCompiler {
1818
1859
  true,
1819
1860
  this.module.i32.const(0),
1820
1861
  );
1862
+ this.module.addGlobal(
1863
+ INIT_ALL_GLOBAL,
1864
+ binaryen.i32,
1865
+ true,
1866
+ this.module.i32.const(0),
1867
+ );
1821
1868
  }
1822
1869
 
1823
1870
  addMathKernel() {
@@ -2072,7 +2119,7 @@ class MirCompiler {
2072
2119
  index,
2073
2120
  typeId,
2074
2121
  kind: "buffer",
2075
- components: ["i32", "i32", "i32", "i32", "f32"],
2122
+ components: ["i32", "i32", "i32", "i32", "f32", "i32"],
2076
2123
  };
2077
2124
  }
2078
2125
  if (type.kind === "buffer_span") {
@@ -2084,7 +2131,7 @@ class MirCompiler {
2084
2131
  index,
2085
2132
  typeId,
2086
2133
  kind: "buffer_span",
2087
- components: ["i32", "i32", "i32", "i32", "i32"],
2134
+ components: ["i32", "i32", "i32", "i32", "i32", "i32"],
2088
2135
  };
2089
2136
  }
2090
2137
  if (type.kind === "array") {
@@ -2113,9 +2160,9 @@ class MirCompiler {
2113
2160
  }
2114
2161
  if (layout.kind === "array") return;
2115
2162
  const suffixes = layout.kind === "buffer"
2116
- ? ["read_address", "write_address", "frames", "channels", "sample_rate"]
2163
+ ? ["read_address", "write_address", "frames", "channels", "sample_rate", "bound"]
2117
2164
  : layout.kind === "buffer_span"
2118
- ? ["read_table", "write_table", "frames_table", "channels_table", "sample_rates_table"]
2165
+ ? ["read_table", "write_table", "frames_table", "channels_table", "sample_rates_table", "bound_table"]
2119
2166
  : ["read_address", "write_address", "length", "stride"];
2120
2167
  for (const [offset, suffix] of suffixes.entries()) {
2121
2168
  binaryen.Function.setLocalName(
@@ -2262,6 +2309,7 @@ class MirCompiler {
2262
2309
  RUNTIME_FAILURE_GLOBAL,
2263
2310
  this.module.i32.const(PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE),
2264
2311
  ),
2312
+ ...this.resetDelegateBatch(),
2265
2313
  this.returnFromCurrentFunction(context),
2266
2314
  ]);
2267
2315
  }
@@ -2295,6 +2343,92 @@ class MirCompiler {
2295
2343
  : this.module.i32.const(PROCESSOR_EXECUTION_OK);
2296
2344
  }
2297
2345
 
2346
+ resetDelegateBatch() {
2347
+ const batch = () =>
2348
+ this.module.global.get(POINTER_GLOBALS.delegateBatch, binaryen.i32);
2349
+ const stores = [
2350
+ DELEGATE_BATCH_USED_OFFSET,
2351
+ DELEGATE_BATCH_RECORD_COUNT_OFFSET,
2352
+ DELEGATE_BATCH_OVERFLOW_OFFSET,
2353
+ ].map((offset) =>
2354
+ this.module.i32.store(
2355
+ offset,
2356
+ 4,
2357
+ batch(),
2358
+ this.module.i32.const(0),
2359
+ )
2360
+ );
2361
+ return [
2362
+ this.module.if(
2363
+ this.module.i32.ne(batch(), this.module.i32.const(0)),
2364
+ this.module.block(null, stores),
2365
+ ),
2366
+ ];
2367
+ }
2368
+
2369
+ executionOutputBatch(outputLocal, fieldOffset) {
2370
+ const output = () => this.module.local.get(outputLocal, binaryen.i32);
2371
+ return this.module.if(
2372
+ this.module.i32.ne(output(), this.module.i32.const(0)),
2373
+ this.module.i32.load(fieldOffset, 4, output()),
2374
+ this.module.i32.const(0),
2375
+ );
2376
+ }
2377
+
2378
+ executionOutputSequence(outputLocal) {
2379
+ const output = () => this.module.local.get(outputLocal, binaryen.i32);
2380
+ return this.module.if(
2381
+ this.module.i32.ne(output(), this.module.i32.const(0)),
2382
+ this.module.i32.add(
2383
+ output(),
2384
+ this.module.i32.const(EXECUTION_OUTPUT_SEQUENCE_OFFSET),
2385
+ ),
2386
+ this.module.i32.const(0),
2387
+ );
2388
+ }
2389
+
2390
+ advanceOutputSequence(sequenceLocal) {
2391
+ const pointer = () =>
2392
+ this.module.global.get(POINTER_GLOBALS.outputSequence, binaryen.i32);
2393
+ const sequence = () => this.module.local.get(sequenceLocal, binaryen.i32);
2394
+ return this.module.block(null, [
2395
+ this.module.local.set(
2396
+ sequenceLocal,
2397
+ this.module.i32.load(0, 4, pointer()),
2398
+ ),
2399
+ this.module.i32.store(
2400
+ 0,
2401
+ 4,
2402
+ pointer(),
2403
+ this.module.select(
2404
+ this.module.i32.eq(sequence(), this.module.i32.const(-1)),
2405
+ sequence(),
2406
+ this.module.i32.add(sequence(), this.module.i32.const(1)),
2407
+ ),
2408
+ ),
2409
+ ]);
2410
+ }
2411
+
2412
+ fullInitClearRanges() {
2413
+ const ranges = [];
2414
+ let cursor = 0;
2415
+ for (const [stateId, slot] of this.mir.state.entries()) {
2416
+ if (slot.pinned !== true) continue;
2417
+ const layout = this.stateLayout[stateId];
2418
+ if (cursor < layout.offset) {
2419
+ ranges.push({ offset: cursor, size: layout.offset - cursor });
2420
+ }
2421
+ cursor = layout.offset + layout.size;
2422
+ }
2423
+ if (cursor < this.stateLayout.byteLength) {
2424
+ ranges.push({
2425
+ offset: cursor,
2426
+ size: this.stateLayout.byteLength - cursor,
2427
+ });
2428
+ }
2429
+ return ranges;
2430
+ }
2431
+
2298
2432
  addAbiWrappers() {
2299
2433
  const initId = this.mir.entry_points.init;
2300
2434
  const processId = this.mir.entry_points.process;
@@ -2307,6 +2441,21 @@ class MirCompiler {
2307
2441
  binaryen.Features.BulkMemoryOpt |
2308
2442
  (this.options.simd ? binaryen.Features.SIMD128 : 0),
2309
2443
  );
2444
+ // Pinned declarations fully initialize their own slots on this path.
2445
+ // Clear only the complementary ranges, including layout padding, so large
2446
+ // pinned arrays are never written once here and again by their initializer.
2447
+ const fullInitClears = this.fullInitClearRanges().map(({ offset, size }) =>
2448
+ this.module.memory.fill(
2449
+ offset === 0
2450
+ ? this.module.local.get(1, binaryen.i32)
2451
+ : this.module.i32.add(
2452
+ this.module.local.get(1, binaryen.i32),
2453
+ this.module.i32.const(offset),
2454
+ ),
2455
+ this.module.i32.const(0),
2456
+ this.module.i32.const(size),
2457
+ )
2458
+ );
2310
2459
  const initBody = this.module.block(null, [
2311
2460
  ...this.resetRuntimeFailure(initId),
2312
2461
  this.module.global.set(
@@ -2317,25 +2466,76 @@ class MirCompiler {
2317
2466
  POINTER_GLOBALS.state,
2318
2467
  this.module.local.get(1, binaryen.i32),
2319
2468
  ),
2320
- this.module.memory.fill(
2321
- this.module.local.get(1, binaryen.i32),
2322
- this.module.i32.const(0),
2323
- this.module.i32.const(this.stateLayout.byteLength ?? 0),
2469
+ this.module.global.set(
2470
+ POINTER_GLOBALS.buffers,
2471
+ this.module.local.get(3, binaryen.i32),
2324
2472
  ),
2473
+ this.module.global.set(
2474
+ POINTER_GLOBALS.bufferWrites,
2475
+ this.module.local.get(3, binaryen.i32),
2476
+ ),
2477
+ this.module.global.set(
2478
+ POINTER_GLOBALS.bufferFrames,
2479
+ this.module.local.get(4, binaryen.i32),
2480
+ ),
2481
+ this.module.global.set(
2482
+ POINTER_GLOBALS.bufferChannels,
2483
+ this.module.local.get(5, binaryen.i32),
2484
+ ),
2485
+ this.module.global.set(
2486
+ POINTER_GLOBALS.bufferSampleRates,
2487
+ this.module.local.get(6, binaryen.i32),
2488
+ ),
2489
+ this.module.global.set(
2490
+ POINTER_GLOBALS.delegateBatch,
2491
+ this.executionOutputBatch(7, EXECUTION_OUTPUT_DELEGATE_BATCH_OFFSET),
2492
+ ),
2493
+ this.module.global.set(
2494
+ POINTER_GLOBALS.printBatch,
2495
+ this.executionOutputBatch(7, EXECUTION_OUTPUT_PRINT_BATCH_OFFSET),
2496
+ ),
2497
+ this.module.global.set(
2498
+ POINTER_GLOBALS.outputSequence,
2499
+ this.executionOutputSequence(7),
2500
+ ),
2501
+ this.module.global.set(
2502
+ INIT_ALL_GLOBAL,
2503
+ this.module.i32.ne(
2504
+ this.module.local.get(2, binaryen.i32),
2505
+ this.module.i32.const(0),
2506
+ ),
2507
+ ),
2508
+ ...(fullInitClears.length === 0
2509
+ ? []
2510
+ : [
2511
+ this.module.if(
2512
+ this.module.global.get(INIT_ALL_GLOBAL, binaryen.i32),
2513
+ this.module.block(null, fullInitClears),
2514
+ ),
2515
+ ]),
2325
2516
  this.module.call(this.functionNames[initId], [], binaryen.none),
2326
2517
  this.executionStatus(initId),
2327
2518
  ], binaryen.i32);
2328
2519
  this.module.addFunction(
2329
2520
  "$onda.abi.init",
2330
- binaryen.createType([binaryen.i32, binaryen.i32]),
2521
+ binaryen.createType([
2522
+ binaryen.i32,
2523
+ binaryen.i32,
2524
+ binaryen.i32,
2525
+ binaryen.i32,
2526
+ binaryen.i32,
2527
+ binaryen.i32,
2528
+ binaryen.i32,
2529
+ binaryen.i32,
2530
+ ]),
2331
2531
  binaryen.i32,
2332
2532
  [],
2333
2533
  initBody,
2334
2534
  );
2335
- this.module.addFunctionExport("$onda.abi.init", "onda_init");
2535
+ this.module.addFunctionExport("$onda.abi.init", "onda_processor_init");
2336
2536
 
2337
2537
  const processParams = binaryen.createType(
2338
- Array.from({ length: 11 }, () => binaryen.i32),
2538
+ Array.from({ length: 12 }, () => binaryen.i32),
2339
2539
  );
2340
2540
  const startFrame = () => this.module.local.get(4, binaryen.i32);
2341
2541
  const frames = () => this.module.local.get(5, binaryen.i32);
@@ -2369,6 +2569,18 @@ class MirCompiler {
2369
2569
  ),
2370
2570
  );
2371
2571
  const processBody = this.module.block(null, [
2572
+ this.module.global.set(
2573
+ POINTER_GLOBALS.delegateBatch,
2574
+ this.executionOutputBatch(11, EXECUTION_OUTPUT_DELEGATE_BATCH_OFFSET),
2575
+ ),
2576
+ this.module.global.set(
2577
+ POINTER_GLOBALS.printBatch,
2578
+ this.executionOutputBatch(11, EXECUTION_OUTPUT_PRINT_BATCH_OFFSET),
2579
+ ),
2580
+ this.module.global.set(
2581
+ POINTER_GLOBALS.outputSequence,
2582
+ this.executionOutputSequence(11),
2583
+ ),
2372
2584
  this.module.if(
2373
2585
  invalidRange,
2374
2586
  this.module.return(
@@ -2441,6 +2653,18 @@ class MirCompiler {
2441
2653
  }
2442
2654
  const wrapperName = `$onda.abi.event.${eventId}`;
2443
2655
  const body = this.module.block(null, [
2656
+ this.module.global.set(
2657
+ POINTER_GLOBALS.delegateBatch,
2658
+ this.executionOutputBatch(7, EXECUTION_OUTPUT_DELEGATE_BATCH_OFFSET),
2659
+ ),
2660
+ this.module.global.set(
2661
+ POINTER_GLOBALS.printBatch,
2662
+ this.executionOutputBatch(7, EXECUTION_OUTPUT_PRINT_BATCH_OFFSET),
2663
+ ),
2664
+ this.module.global.set(
2665
+ POINTER_GLOBALS.outputSequence,
2666
+ this.executionOutputSequence(7),
2667
+ ),
2444
2668
  ...this.resetRuntimeFailure(event.handler),
2445
2669
  this.module.global.set(
2446
2670
  POINTER_GLOBALS.eventPayload,
@@ -2479,7 +2703,7 @@ class MirCompiler {
2479
2703
  ], binaryen.i32);
2480
2704
  this.module.addFunction(
2481
2705
  wrapperName,
2482
- binaryen.createType(Array.from({ length: 7 }, () => binaryen.i32)),
2706
+ binaryen.createType(Array.from({ length: 8 }, () => binaryen.i32)),
2483
2707
  binaryen.i32,
2484
2708
  [],
2485
2709
  body,
@@ -2527,6 +2751,10 @@ class MirCompiler {
2527
2751
  }
2528
2752
  case "call":
2529
2753
  return this.compileCall(data, context);
2754
+ case "publish_delegate":
2755
+ return this.compilePublishDelegate(data, context);
2756
+ case "publish_log":
2757
+ return this.compilePublishLog(data, context);
2530
2758
  case "output_store":
2531
2759
  return this.compileOutputStore(data, context);
2532
2760
  case "control_output_store":
@@ -2792,6 +3020,561 @@ class MirCompiler {
2792
3020
  ]);
2793
3021
  }
2794
3022
 
3023
+ compilePublishDelegate(data, context) {
3024
+ const delegate = this.mir.interface.delegates[data.delegate];
3025
+ const layout = this.delegateLayout[data.delegate];
3026
+ if (!delegate || !layout || data.args?.length !== delegate.params.length) {
3027
+ this.fail(`delegate id ${String(data.delegate)} has an invalid publication payload`);
3028
+ }
3029
+ if (context.function.attributes.runtime_context !== true) {
3030
+ this.fail(`function '${context.function.name}' publishes without runtime context`);
3031
+ }
3032
+
3033
+ const payloadBytes = this.allocateGeneratedLocal(
3034
+ context,
3035
+ "i32",
3036
+ `delegate.${data.delegate}.payload_bytes`,
3037
+ );
3038
+ const oversized = this.allocateGeneratedLocal(
3039
+ context,
3040
+ "i32",
3041
+ `delegate.${data.delegate}.oversized`,
3042
+ );
3043
+ const record = this.allocateGeneratedLocal(
3044
+ context,
3045
+ "i32",
3046
+ `delegate.${data.delegate}.record`,
3047
+ );
3048
+ const cursor = this.allocateGeneratedLocal(
3049
+ context,
3050
+ "i32",
3051
+ `delegate.${data.delegate}.cursor`,
3052
+ );
3053
+ const counter = this.allocateGeneratedLocal(
3054
+ context,
3055
+ "i32",
3056
+ `delegate.${data.delegate}.copy_index`,
3057
+ );
3058
+ const payload = () => this.module.local.get(payloadBytes, binaryen.i32);
3059
+ const tooLarge = () => this.module.local.get(oversized, binaryen.i32);
3060
+ const batch = () =>
3061
+ this.module.global.get(POINTER_GLOBALS.delegateBatch, binaryen.i32);
3062
+ const validationStatements = [];
3063
+ const collectionStatements = [
3064
+ this.module.local.set(
3065
+ payloadBytes,
3066
+ this.module.i32.const(layout.minimumByteLength),
3067
+ ),
3068
+ this.module.local.set(
3069
+ oversized,
3070
+ this.module.i32.const(
3071
+ layout.minimumByteLength > 0xffff_ffff - DELEGATE_RECORD_HEADER_SIZE
3072
+ ? 1
3073
+ : 0,
3074
+ ),
3075
+ ),
3076
+ ];
3077
+
3078
+ for (const [paramId, param] of delegate.params.entries()) {
3079
+ const type = this.type(param.ty);
3080
+ const argument = data.args[paramId];
3081
+ if (argument?.kind !== "value") {
3082
+ this.fail(`delegate '${delegate.name}' argument ${paramId} is not a value`);
3083
+ }
3084
+ if (type.kind !== "slice") continue;
3085
+ const element = type.data.element;
3086
+ const elementSize = this.scalarSize(element);
3087
+ const length = () => this.compileSliceValue(argument.data, context)[2];
3088
+ const delta = () =>
3089
+ this.module.i32.mul(length(), this.module.i32.const(elementSize));
3090
+ const next = () => this.module.i32.add(payload(), delta());
3091
+ collectionStatements.push(
3092
+ this.module.local.set(
3093
+ oversized,
3094
+ this.module.i32.or(
3095
+ tooLarge(),
3096
+ this.module.i32.or(
3097
+ this.module.i32.gt_u(
3098
+ length(),
3099
+ this.module.i32.const(Math.floor(0xffff_ffff / elementSize)),
3100
+ ),
3101
+ this.module.i32.lt_u(next(), payload()),
3102
+ ),
3103
+ ),
3104
+ ),
3105
+ this.module.local.set(payloadBytes, next()),
3106
+ );
3107
+ }
3108
+ for (const [paramId, param] of delegate.params.entries()) {
3109
+ const type = this.type(param.ty);
3110
+ if (type.kind !== "array") continue;
3111
+ const argument = data.args[paramId];
3112
+ const sliceLength = () => this.compileSliceValue(argument.data, context)[2];
3113
+ validationStatements.push(
3114
+ this.module.if(
3115
+ this.module.i32.ne(
3116
+ sliceLength(),
3117
+ this.module.i32.const(type.data.len),
3118
+ ),
3119
+ this.raiseRuntimeFailure(context),
3120
+ ),
3121
+ );
3122
+ }
3123
+ collectionStatements.push(
3124
+ this.module.local.set(
3125
+ oversized,
3126
+ this.module.i32.or(
3127
+ tooLarge(),
3128
+ this.module.i32.gt_u(
3129
+ payload(),
3130
+ this.module.i32.const(0xffff_ffff - DELEGATE_RECORD_HEADER_SIZE),
3131
+ ),
3132
+ ),
3133
+ ),
3134
+ this.compileDelegateBatchAppend(
3135
+ data.delegate,
3136
+ delegate,
3137
+ data.args,
3138
+ payload,
3139
+ tooLarge,
3140
+ record,
3141
+ cursor,
3142
+ counter,
3143
+ context,
3144
+ ),
3145
+ );
3146
+ return this.module.block(null, [
3147
+ ...validationStatements,
3148
+ this.module.if(
3149
+ this.module.i32.ne(batch(), this.module.i32.const(0)),
3150
+ this.module.block(null, collectionStatements),
3151
+ ),
3152
+ ]);
3153
+ }
3154
+
3155
+ compilePublishLog(data, context) {
3156
+ const site = this.mir.log_sites?.[data.site];
3157
+ const args = data.arguments;
3158
+ if (!site || !Array.isArray(args) || args.length !== site.argument_types?.length) {
3159
+ this.fail(`log site id ${String(data.site)} has an invalid publication payload`);
3160
+ }
3161
+ if (context.function.attributes.runtime_context !== true) {
3162
+ this.fail(`function '${context.function.name}' prints without runtime context`);
3163
+ }
3164
+
3165
+ const payloadSize = site.payload_size;
3166
+ const calculatedSize = site.argument_types.reduce(
3167
+ (size, scalar) => size + this.scalarSize(scalar),
3168
+ 0,
3169
+ );
3170
+ if (!Number.isInteger(payloadSize) || payloadSize !== calculatedSize) {
3171
+ this.fail(`log site id ${data.site} has an invalid payload size`);
3172
+ }
3173
+
3174
+ const storageLocal = this.allocateGeneratedLocal(
3175
+ context,
3176
+ "i32",
3177
+ `log.${data.site}.storage`,
3178
+ );
3179
+ const capacityLocal = this.allocateGeneratedLocal(
3180
+ context,
3181
+ "i32",
3182
+ `log.${data.site}.capacity`,
3183
+ );
3184
+ const usedLocal = this.allocateGeneratedLocal(
3185
+ context,
3186
+ "i32",
3187
+ `log.${data.site}.used`,
3188
+ );
3189
+ const recordLocal = this.allocateGeneratedLocal(
3190
+ context,
3191
+ "i32",
3192
+ `log.${data.site}.record`,
3193
+ );
3194
+ const cursorLocal = this.allocateGeneratedLocal(
3195
+ context,
3196
+ "i32",
3197
+ `log.${data.site}.cursor`,
3198
+ );
3199
+ const sequenceLocal = this.allocateGeneratedLocal(
3200
+ context,
3201
+ "i32",
3202
+ `log.${data.site}.sequence`,
3203
+ );
3204
+ const batch = () =>
3205
+ this.module.global.get(POINTER_GLOBALS.printBatch, binaryen.i32);
3206
+ const field = (offset) =>
3207
+ this.module.i32.add(batch(), this.module.i32.const(offset));
3208
+ const local = (id) => this.module.local.get(id, binaryen.i32);
3209
+ const requiredSize = payloadSize + PRINT_RECORD_HEADER_SIZE;
3210
+ const overflowAddress = () => field(PRINT_BATCH_OVERFLOW_OFFSET);
3211
+ const overflow = () => this.module.i32.load(0, 4, overflowAddress());
3212
+ const drop = this.module.i32.store(
3213
+ 0,
3214
+ 4,
3215
+ overflowAddress(),
3216
+ this.module.select(
3217
+ this.module.i32.eq(overflow(), this.module.i32.const(-1)),
3218
+ overflow(),
3219
+ this.module.i32.add(overflow(), this.module.i32.const(1)),
3220
+ ),
3221
+ );
3222
+ const fits = this.module.i32.and(
3223
+ this.module.i32.le_u(local(usedLocal), local(capacityLocal)),
3224
+ this.module.i32.le_u(
3225
+ this.module.i32.const(requiredSize),
3226
+ this.module.i32.sub(local(capacityLocal), local(usedLocal)),
3227
+ ),
3228
+ );
3229
+ const record = () => local(recordLocal);
3230
+ const cursor = () => local(cursorLocal);
3231
+ const write = [
3232
+ this.module.local.set(
3233
+ recordLocal,
3234
+ this.module.i32.add(local(storageLocal), local(usedLocal)),
3235
+ ),
3236
+ this.module.i32.store(0, 1, record(), this.module.i32.const(data.site)),
3237
+ this.module.i32.store(4, 1, record(), this.module.i32.const(payloadSize)),
3238
+ this.module.i32.store(8, 1, record(), local(sequenceLocal)),
3239
+ this.module.local.set(
3240
+ cursorLocal,
3241
+ this.module.i32.add(record(), this.module.i32.const(PRINT_RECORD_HEADER_SIZE)),
3242
+ ),
3243
+ ];
3244
+ for (const [index, scalar] of site.argument_types.entries()) {
3245
+ write.push(
3246
+ this.storePackedScalar(
3247
+ scalar,
3248
+ cursor(),
3249
+ this.compileValue(args[index], context),
3250
+ ),
3251
+ this.module.local.set(
3252
+ cursorLocal,
3253
+ this.module.i32.add(cursor(), this.module.i32.const(this.scalarSize(scalar))),
3254
+ ),
3255
+ );
3256
+ }
3257
+ const usedAddress = () => field(PRINT_BATCH_USED_OFFSET);
3258
+ const countAddress = () => field(PRINT_BATCH_RECORD_COUNT_OFFSET);
3259
+ write.push(
3260
+ this.module.i32.store(
3261
+ 0,
3262
+ 4,
3263
+ usedAddress(),
3264
+ this.module.i32.add(local(usedLocal), this.module.i32.const(requiredSize)),
3265
+ ),
3266
+ this.module.i32.store(
3267
+ 0,
3268
+ 4,
3269
+ countAddress(),
3270
+ this.module.i32.add(
3271
+ this.module.i32.load(0, 4, countAddress()),
3272
+ this.module.i32.const(1),
3273
+ ),
3274
+ ),
3275
+ );
3276
+ return this.module.if(
3277
+ this.module.i32.ne(batch(), this.module.i32.const(0)),
3278
+ this.module.block(null, [
3279
+ this.advanceOutputSequence(sequenceLocal),
3280
+ this.module.local.set(
3281
+ storageLocal,
3282
+ this.module.i32.load(0, 4, field(PRINT_BATCH_STORAGE_OFFSET)),
3283
+ ),
3284
+ this.module.if(
3285
+ this.module.i32.ne(local(storageLocal), this.module.i32.const(0)),
3286
+ this.module.block(null, [
3287
+ this.module.local.set(
3288
+ capacityLocal,
3289
+ this.module.i32.load(0, 4, field(PRINT_BATCH_CAPACITY_OFFSET)),
3290
+ ),
3291
+ this.module.local.set(
3292
+ usedLocal,
3293
+ this.module.i32.load(0, 4, field(PRINT_BATCH_USED_OFFSET)),
3294
+ ),
3295
+ this.module.if(fits, this.module.block(null, write), drop),
3296
+ ]),
3297
+ ),
3298
+ ]),
3299
+ );
3300
+ }
3301
+
3302
+ compileDelegateBatchAppend(
3303
+ delegateId,
3304
+ delegate,
3305
+ args,
3306
+ payload,
3307
+ tooLarge,
3308
+ recordLocal,
3309
+ cursorLocal,
3310
+ counterLocal,
3311
+ context,
3312
+ ) {
3313
+ const batch = () =>
3314
+ this.module.global.get(POINTER_GLOBALS.delegateBatch, binaryen.i32);
3315
+ const field = (offset) =>
3316
+ this.module.i32.add(batch(), this.module.i32.const(offset));
3317
+ const storage = this.allocateGeneratedLocal(
3318
+ context,
3319
+ "i32",
3320
+ `delegate.${delegateId}.storage`,
3321
+ );
3322
+ const capacity = this.allocateGeneratedLocal(
3323
+ context,
3324
+ "i32",
3325
+ `delegate.${delegateId}.capacity`,
3326
+ );
3327
+ const used = this.allocateGeneratedLocal(
3328
+ context,
3329
+ "i32",
3330
+ `delegate.${delegateId}.used`,
3331
+ );
3332
+ const required = this.allocateGeneratedLocal(
3333
+ context,
3334
+ "i32",
3335
+ `delegate.${delegateId}.required`,
3336
+ );
3337
+ const sequence = this.allocateGeneratedLocal(
3338
+ context,
3339
+ "i32",
3340
+ `delegate.${delegateId}.sequence`,
3341
+ );
3342
+ const local = (id) => this.module.local.get(id, binaryen.i32);
3343
+ const write = this.compileDelegateRecord(
3344
+ delegateId,
3345
+ delegate,
3346
+ args,
3347
+ payload,
3348
+ () => local(storage),
3349
+ () => local(used),
3350
+ recordLocal,
3351
+ cursorLocal,
3352
+ counterLocal,
3353
+ sequence,
3354
+ context,
3355
+ );
3356
+ const overflowAddress = () => field(DELEGATE_BATCH_OVERFLOW_OFFSET);
3357
+ const overflow = () => this.module.i32.load(0, 4, overflowAddress());
3358
+ const drop = this.module.i32.store(
3359
+ 0,
3360
+ 4,
3361
+ overflowAddress(),
3362
+ this.module.select(
3363
+ this.module.i32.eq(overflow(), this.module.i32.const(-1)),
3364
+ overflow(),
3365
+ this.module.i32.add(overflow(), this.module.i32.const(1)),
3366
+ ),
3367
+ );
3368
+ const fit = this.module.i32.and(
3369
+ this.module.i32.eq(tooLarge(), this.module.i32.const(0)),
3370
+ this.module.i32.and(
3371
+ this.module.i32.le_u(local(used), local(capacity)),
3372
+ this.module.i32.le_u(
3373
+ local(required),
3374
+ this.module.i32.sub(local(capacity), local(used)),
3375
+ ),
3376
+ ),
3377
+ );
3378
+ return this.module.block(null, [
3379
+ this.advanceOutputSequence(sequence),
3380
+ this.module.local.set(
3381
+ storage,
3382
+ this.module.i32.load(0, 4, field(DELEGATE_BATCH_STORAGE_OFFSET)),
3383
+ ),
3384
+ this.module.if(
3385
+ this.module.i32.ne(local(storage), this.module.i32.const(0)),
3386
+ this.module.block(null, [
3387
+ this.module.local.set(
3388
+ capacity,
3389
+ this.module.i32.load(0, 4, field(DELEGATE_BATCH_CAPACITY_OFFSET)),
3390
+ ),
3391
+ this.module.local.set(
3392
+ used,
3393
+ this.module.i32.load(0, 4, field(DELEGATE_BATCH_USED_OFFSET)),
3394
+ ),
3395
+ this.module.local.set(
3396
+ required,
3397
+ this.module.i32.add(
3398
+ payload(),
3399
+ this.module.i32.const(DELEGATE_RECORD_HEADER_SIZE),
3400
+ ),
3401
+ ),
3402
+ this.module.if(fit, write, drop),
3403
+ ]),
3404
+ ),
3405
+ ]);
3406
+ }
3407
+
3408
+ compileDelegateRecord(
3409
+ delegateId,
3410
+ delegate,
3411
+ args,
3412
+ payload,
3413
+ storage,
3414
+ used,
3415
+ recordLocal,
3416
+ cursorLocal,
3417
+ counterLocal,
3418
+ sequenceLocal,
3419
+ context,
3420
+ ) {
3421
+ const batch = () =>
3422
+ this.module.global.get(POINTER_GLOBALS.delegateBatch, binaryen.i32);
3423
+ const record = () => this.module.local.get(recordLocal, binaryen.i32);
3424
+ const cursor = () => this.module.local.get(cursorLocal, binaryen.i32);
3425
+ const statements = [];
3426
+ statements.push(
3427
+ this.module.local.set(recordLocal, this.module.i32.add(storage(), used())),
3428
+ this.module.i32.store(0, 1, record(), this.module.i32.const(delegateId)),
3429
+ this.module.i32.store(4, 1, record(), payload()),
3430
+ this.module.i32.store(
3431
+ 8,
3432
+ 1,
3433
+ record(),
3434
+ this.module.local.get(sequenceLocal, binaryen.i32),
3435
+ ),
3436
+ this.module.local.set(
3437
+ cursorLocal,
3438
+ this.module.i32.add(
3439
+ record(),
3440
+ this.module.i32.const(DELEGATE_RECORD_HEADER_SIZE),
3441
+ ),
3442
+ ),
3443
+ );
3444
+ for (const [paramId, param] of delegate.params.entries()) {
3445
+ const type = this.type(param.ty);
3446
+ const argument = args[paramId];
3447
+ if (type.kind === "scalar") {
3448
+ statements.push(
3449
+ this.storePackedScalar(
3450
+ type.data,
3451
+ cursor(),
3452
+ this.compileValue(argument.data, context),
3453
+ ),
3454
+ this.module.local.set(
3455
+ cursorLocal,
3456
+ this.module.i32.add(
3457
+ cursor(),
3458
+ this.module.i32.const(this.scalarSize(type.data)),
3459
+ ),
3460
+ ),
3461
+ );
3462
+ continue;
3463
+ }
3464
+ const element = type.kind === "slice"
3465
+ ? type.data.element
3466
+ : this.requireScalarType(
3467
+ type.data.element,
3468
+ `delegate '${delegate.name}' aggregate parameter ${paramId}`,
3469
+ );
3470
+ const slice = () => this.compileSliceValue(argument.data, context);
3471
+ const count = type.kind === "array"
3472
+ ? () => this.module.i32.const(type.data.len)
3473
+ : () => slice()[2];
3474
+ if (type.kind === "slice") {
3475
+ statements.push(
3476
+ this.module.i32.store(0, 1, cursor(), count()),
3477
+ this.module.local.set(
3478
+ cursorLocal,
3479
+ this.module.i32.add(cursor(), this.module.i32.const(4)),
3480
+ ),
3481
+ );
3482
+ }
3483
+ statements.push(
3484
+ this.compilePackedSliceCopy(
3485
+ slice,
3486
+ count,
3487
+ element,
3488
+ cursor,
3489
+ counterLocal,
3490
+ ),
3491
+ this.module.local.set(
3492
+ cursorLocal,
3493
+ this.module.i32.add(
3494
+ cursor(),
3495
+ this.module.i32.mul(
3496
+ count(),
3497
+ this.module.i32.const(this.scalarSize(element)),
3498
+ ),
3499
+ ),
3500
+ ),
3501
+ );
3502
+ }
3503
+ const usedAddress = () =>
3504
+ this.module.i32.add(
3505
+ batch(),
3506
+ this.module.i32.const(DELEGATE_BATCH_USED_OFFSET),
3507
+ );
3508
+ const countAddress = () =>
3509
+ this.module.i32.add(
3510
+ batch(),
3511
+ this.module.i32.const(DELEGATE_BATCH_RECORD_COUNT_OFFSET),
3512
+ );
3513
+ statements.push(
3514
+ this.module.i32.store(
3515
+ 0,
3516
+ 4,
3517
+ usedAddress(),
3518
+ this.module.i32.add(
3519
+ used(),
3520
+ this.module.i32.add(
3521
+ payload(),
3522
+ this.module.i32.const(DELEGATE_RECORD_HEADER_SIZE),
3523
+ ),
3524
+ ),
3525
+ ),
3526
+ this.module.i32.store(
3527
+ 0,
3528
+ 4,
3529
+ countAddress(),
3530
+ this.module.i32.add(
3531
+ this.module.i32.load(0, 4, countAddress()),
3532
+ this.module.i32.const(1),
3533
+ ),
3534
+ ),
3535
+ );
3536
+ return this.module.block(null, statements);
3537
+ }
3538
+
3539
+ compilePackedSliceCopy(slice, count, scalar, destination, counterLocal) {
3540
+ const loopLabel = `$onda.delegate.copy.${this.nextLabel++}`;
3541
+ const counter = () => this.module.local.get(counterLocal, binaryen.i32);
3542
+ const sourceAddress = () =>
3543
+ this.module.i32.add(
3544
+ slice()[0],
3545
+ this.module.i32.mul(counter(), slice()[3]),
3546
+ );
3547
+ const destinationAddress = () =>
3548
+ this.module.i32.add(
3549
+ destination(),
3550
+ this.module.i32.mul(
3551
+ counter(),
3552
+ this.module.i32.const(this.scalarSize(scalar)),
3553
+ ),
3554
+ );
3555
+ return this.module.block(null, [
3556
+ this.module.local.set(counterLocal, this.module.i32.const(0)),
3557
+ this.module.loop(
3558
+ loopLabel,
3559
+ this.module.if(
3560
+ this.module.i32.lt_u(counter(), count()),
3561
+ this.module.block(null, [
3562
+ this.storePackedScalar(
3563
+ scalar,
3564
+ destinationAddress(),
3565
+ this.loadScalar(scalar, sourceAddress()),
3566
+ ),
3567
+ this.module.local.set(
3568
+ counterLocal,
3569
+ this.module.i32.add(counter(), this.module.i32.const(1)),
3570
+ ),
3571
+ this.module.br(loopLabel),
3572
+ ]),
3573
+ ),
3574
+ ),
3575
+ ]);
3576
+ }
3577
+
2795
3578
  compileOutputStore(data, context) {
2796
3579
  this.requireProcessFrame(data.frame, context, "audio output store");
2797
3580
  const port = this.outputLayout[data.output];
@@ -2925,6 +3708,11 @@ class MirCompiler {
2925
3708
  );
2926
3709
  case "intrinsic":
2927
3710
  return this.compileIntrinsic(data, expectedScalar, context);
3711
+ case "init_all":
3712
+ if (context.function.kind?.kind !== "init") {
3713
+ this.fail("init_all is only valid in the init entry point");
3714
+ }
3715
+ return this.module.global.get(INIT_ALL_GLOBAL, binaryen.i32);
2928
3716
  case "process_frame":
2929
3717
  return this.compileProcessFrame(data, context);
2930
3718
  case "input_load":
@@ -2954,6 +3742,10 @@ class MirCompiler {
2954
3742
  );
2955
3743
  case "buffer_param_sample_rate":
2956
3744
  return this.loadBufferParamComponent(data, 4, "f32", context);
3745
+ case "buffer_is_bound":
3746
+ return this.compileBufferIsBound(data, context);
3747
+ case "buffer_param_is_bound":
3748
+ return this.loadBufferParamComponent(data, 5, "i32", context);
2957
3749
  case "slice_len":
2958
3750
  return this.compileSliceValue(data, context)[2];
2959
3751
  case "slice_load":
@@ -3356,6 +4148,20 @@ class MirCompiler {
3356
4148
  );
3357
4149
  }
3358
4150
 
4151
+ compileBufferIsBound(bufferRef, context) {
4152
+ this.requireBufferRef(bufferRef);
4153
+ return this.withBufferRefIndex(
4154
+ bufferRef,
4155
+ context,
4156
+ binaryen.i32,
4157
+ (index, staticIndex) => this.bufferBoundFactory(
4158
+ index,
4159
+ staticIndex,
4160
+ context,
4161
+ )(),
4162
+ );
4163
+ }
4164
+
3359
4165
  compileBufferChannels(bufferRef, context) {
3360
4166
  const channels = this.bufferRefChannelMetadata(bufferRef);
3361
4167
  if (channels.kind === "mono") return this.module.i32.const(1);
@@ -3543,6 +4349,9 @@ class MirCompiler {
3543
4349
  return this.loadScalar(scalar, address);
3544
4350
  };
3545
4351
  const load = (index) => {
4352
+ if (offset === 5) {
4353
+ return this.module.i32.ne(rawLoad(index), this.module.i32.const(0));
4354
+ }
3546
4355
  if (scalar !== "i32" || (offset !== 0 && offset !== 1)) {
3547
4356
  return rawLoad(index);
3548
4357
  }
@@ -3615,7 +4424,7 @@ class MirCompiler {
3615
4424
  }
3616
4425
 
3617
4426
  loadBufferParamValue(parameterId, context) {
3618
- const components = ["i32", "i32", "i32", "i32", "f32"];
4427
+ const components = ["i32", "i32", "i32", "i32", "f32", "i32"];
3619
4428
  if (parameterId?.kind !== "array_element") {
3620
4429
  return components.map((scalar, offset) =>
3621
4430
  this.loadBufferParamComponent(parameterId, offset, scalar, context),
@@ -3706,6 +4515,7 @@ class MirCompiler {
3706
4515
  ? this.module.i32.const(channels.count)
3707
4516
  : component(POINTER_GLOBALS.bufferChannels, "i32"),
3708
4517
  component(POINTER_GLOBALS.bufferSampleRates, "f32"),
4518
+ this.bufferBoundFactory(descriptorIndex, staticIndex, context)(),
3709
4519
  ];
3710
4520
  if (initializeIndex !== null) {
3711
4521
  values[0] = this.module.block(
@@ -3728,8 +4538,9 @@ class MirCompiler {
3728
4538
  POINTER_GLOBALS.bufferFrames,
3729
4539
  POINTER_GLOBALS.bufferChannels,
3730
4540
  POINTER_GLOBALS.bufferSampleRates,
4541
+ POINTER_GLOBALS.buffers,
3731
4542
  ];
3732
- const tableScalars = ["i32", "i32", "i32", "i32", "f32"];
4543
+ const tableScalars = ["i32", "i32", "i32", "i32", "f32", "i32"];
3733
4544
  let tables;
3734
4545
  let start;
3735
4546
  if (spanRef.kind === "interface") {
@@ -3833,6 +4644,31 @@ class MirCompiler {
3833
4644
  return () => this.module.local.get(local, this.wasmType(scalar));
3834
4645
  }
3835
4646
 
4647
+ bufferBoundFactory(descriptorIndex, staticIndex, context) {
4648
+ const load = () => this.module.i32.ne(
4649
+ this.loadBufferTableValueAt(
4650
+ POINTER_GLOBALS.buffers,
4651
+ descriptorIndex(),
4652
+ "i32",
4653
+ ),
4654
+ this.module.i32.const(0),
4655
+ );
4656
+ if (staticIndex === null) return load;
4657
+
4658
+ const key = `buffer_bound:${staticIndex}`;
4659
+ let local = context.bufferDescriptorCache.get(key);
4660
+ if (local === undefined) {
4661
+ local = this.allocateGeneratedLocal(
4662
+ context,
4663
+ "i32",
4664
+ `buffer.bound.${staticIndex}`,
4665
+ );
4666
+ context.bufferDescriptorCache.set(key, local);
4667
+ context.entryInitializers.push(this.module.local.set(local, load()));
4668
+ }
4669
+ return () => this.module.local.get(local, binaryen.i32);
4670
+ }
4671
+
3836
4672
  bufferChannelsFactory(
3837
4673
  bufferRef,
3838
4674
  descriptorIndex,
@@ -5287,6 +6123,62 @@ class MirCompiler {
5287
6123
  arg(0),
5288
6124
  ),
5289
6125
  );
6126
+ case "range_wrap": { // Bounds are validator-required integer constants.
6127
+ const lowerLiteral = data.args[1]?.data;
6128
+ const upperLiteral = data.args[2]?.data;
6129
+ if (
6130
+ data.args[1]?.kind !== "constant"
6131
+ || data.args[2]?.kind !== "constant"
6132
+ || lowerLiteral?.type !== scalar
6133
+ || upperLiteral?.type !== scalar
6134
+ ) {
6135
+ this.fail("range_wrap requires constant bounds matching its integer operand");
6136
+ }
6137
+ const lower = scalar === "i64"
6138
+ ? decodeI64Literal(lowerLiteral.value, this)
6139
+ : BigInt(lowerLiteral.value);
6140
+ const upper = scalar === "i64"
6141
+ ? decodeI64Literal(upperLiteral.value, this)
6142
+ : BigInt(upperLiteral.value);
6143
+ const bits = scalar === "i64" ? 64n : 32n;
6144
+ const width = upper - lower + 1n;
6145
+ if (width === (1n << bits)) return arg(0);
6146
+ const encodedWidth = BigInt.asIntN(Number(bits), width);
6147
+ const widthValue = scalar === "i64"
6148
+ ? this.module.i64.const(encodedWidth)
6149
+ : this.module.i32.const(Number(encodedWidth));
6150
+ const encodedSpan = BigInt.asIntN(Number(bits), width - 1n);
6151
+ const spanValue = scalar === "i64"
6152
+ ? this.module.i64.const(encodedSpan)
6153
+ : this.module.i32.const(Number(encodedSpan));
6154
+ const one = scalar === "i64"
6155
+ ? this.module.i64.const(1n)
6156
+ : this.module.i32.const(1);
6157
+ const distanceFromLower = lower === 0n
6158
+ ? arg(0)
6159
+ : wasm.sub(arg(0), arg(1));
6160
+ return this.module.if(
6161
+ wasm.le_u(distanceFromLower, spanValue),
6162
+ arg(0),
6163
+ this.module.if(
6164
+ wasm.lt_s(arg(0), arg(1)),
6165
+ wasm.sub(
6166
+ arg(2),
6167
+ wasm.rem_u(
6168
+ wasm.sub(wasm.sub(arg(1), one), arg(0)),
6169
+ widthValue,
6170
+ ),
6171
+ ),
6172
+ wasm.add(
6173
+ arg(1),
6174
+ wasm.rem_u(
6175
+ wasm.sub(arg(0), wasm.add(arg(2), one)),
6176
+ widthValue,
6177
+ ),
6178
+ ),
6179
+ ),
6180
+ );
6181
+ }
5290
6182
  default:
5291
6183
  this.fail(`intrinsic '${data.intrinsic}' requires f32 or f64 operands`);
5292
6184
  }
@@ -5395,6 +6287,17 @@ class MirCompiler {
5395
6287
  }
5396
6288
  }
5397
6289
 
6290
+ storePackedScalar(scalar, address, value) {
6291
+ switch (scalar) {
6292
+ case "bool": return this.module.i32.store8(0, 1, address, value);
6293
+ case "i32": return this.module.i32.store(0, 1, address, value);
6294
+ case "i64": return this.module.i64.store(0, 1, address, value);
6295
+ case "f32": return this.module.f32.store(0, 1, address, value);
6296
+ case "f64": return this.module.f64.store(0, 1, address, value);
6297
+ default: this.fail(`unknown scalar type '${String(scalar)}'`);
6298
+ }
6299
+ }
6300
+
5398
6301
  type(typeId) {
5399
6302
  const type = this.mir.types[typeId];
5400
6303
  if (!type) this.fail(`type id ${typeId} is out of range`);
@@ -5632,7 +6535,7 @@ class MirCompiler {
5632
6535
  const requiredExports = [
5633
6536
  "memory",
5634
6537
  "__heap_base",
5635
- "onda_init",
6538
+ "onda_processor_init",
5636
6539
  "onda_process",
5637
6540
  ...eventExports,
5638
6541
  ];
@@ -5687,7 +6590,7 @@ class MirCompiler {
5687
6590
  exports: {
5688
6591
  memory: "memory",
5689
6592
  heap_base: "__heap_base",
5690
- init: "onda_init",
6593
+ init: "onda_processor_init",
5691
6594
  process: "onda_process",
5692
6595
  events: eventExports,
5693
6596
  },
@@ -5702,8 +6605,28 @@ class MirCompiler {
5702
6605
  param_size_bytes: paramSize,
5703
6606
  param_align_bytes: 16,
5704
6607
  requires_full_blocks: false,
6608
+ delegate_record_header_size_bytes: DELEGATE_RECORD_HEADER_SIZE,
6609
+ print_record_header_size_bytes: PRINT_RECORD_HEADER_SIZE,
5705
6610
  },
5706
6611
  metadata: {
6612
+ source_files: (this.mir.source_files ?? []).map((file) => ({
6613
+ path: file.path,
6614
+ })),
6615
+ log_sites: (this.mir.log_sites ?? []).map((site, index) => ({
6616
+ index,
6617
+ label: site.label ?? null,
6618
+ source: {
6619
+ file: site.source.file ?? null,
6620
+ line: site.source.line,
6621
+ column: site.source.column,
6622
+ end_line: site.source.end_line,
6623
+ end_column: site.source.end_column,
6624
+ },
6625
+ lexical_owner: site.lexical_owner,
6626
+ declaration: site.declaration ?? null,
6627
+ argument_types: [...site.argument_types],
6628
+ payload_size_bytes: site.payload_size,
6629
+ })),
5707
6630
  states: snapshot.entries,
5708
6631
  inputs: this.portMetadata(this.mir.interface.inputs, this.inputLayout),
5709
6632
  outputs: this.portMetadata(this.mir.interface.outputs, this.outputLayout),
@@ -5772,20 +6695,43 @@ class MirCompiler {
5772
6695
  payload_size_bytes: this.eventLayout[eventId].byteLength,
5773
6696
  payload_min_size_bytes: this.eventLayout[eventId].minimumByteLength,
5774
6697
  has_dynamic_payload: this.eventLayout[eventId].dynamic,
5775
- params: event.params.map((param, paramId) => ({
5776
- name: param.name,
5777
- type_repr: typeName(this.type(param.ty), this),
5778
- scalar: this.storageShape(param.ty).scalar,
5779
- array_len: this.storageShape(param.ty).length ?? 0,
5780
- is_slice: this.storageShape(param.ty).isSlice === true,
5781
- byte_offset: this.eventLayout[eventId][paramId].offset,
5782
- byte_size: this.eventLayout[eventId][paramId].size,
5783
- element_size_bytes: this.scalarSize(
5784
- this.storageShape(param.ty).scalar,
5785
- ),
5786
- has_default: param.default !== null && param.default !== undefined,
5787
- default_reprs: this.constantReprs(param.default),
5788
- })),
6698
+ params: event.params.map((param, paramId) => {
6699
+ const shape = this.storageShape(param.ty);
6700
+ return {
6701
+ name: param.name,
6702
+ type_repr: typeName(this.type(param.ty), this),
6703
+ scalar: shape.scalar,
6704
+ array_len: shape.length ?? 0,
6705
+ is_array: shape.isArray === true && shape.isSlice !== true,
6706
+ is_slice: shape.isSlice === true,
6707
+ byte_offset: this.eventLayout[eventId][paramId].offset,
6708
+ byte_size: this.eventLayout[eventId][paramId].size,
6709
+ element_size_bytes: this.scalarSize(shape.scalar),
6710
+ has_default: param.default !== null && param.default !== undefined,
6711
+ default_reprs: this.constantReprs(param.default),
6712
+ };
6713
+ }),
6714
+ })),
6715
+ delegates: this.mir.interface.delegates.map((delegate, delegateId) => ({
6716
+ index: delegateId,
6717
+ name: delegate.name,
6718
+ payload_size_bytes: this.delegateLayout[delegateId].byteLength,
6719
+ payload_min_size_bytes: this.delegateLayout[delegateId].minimumByteLength,
6720
+ has_dynamic_payload: this.delegateLayout[delegateId].dynamic,
6721
+ params: delegate.params.map((param, paramId) => {
6722
+ const shape = this.storageShape(param.ty);
6723
+ return {
6724
+ name: param.name,
6725
+ type_repr: typeName(this.type(param.ty), this),
6726
+ scalar: shape.scalar,
6727
+ array_len: shape.length ?? 0,
6728
+ is_array: shape.isArray === true && shape.isSlice !== true,
6729
+ is_slice: shape.isSlice === true,
6730
+ byte_offset: this.delegateLayout[delegateId][paramId].offset,
6731
+ byte_size: this.delegateLayout[delegateId][paramId].size,
6732
+ element_size_bytes: this.scalarSize(shape.scalar),
6733
+ };
6734
+ }),
5789
6735
  })),
5790
6736
  },
5791
6737
  };
@@ -5802,6 +6748,7 @@ class MirCompiler {
5802
6748
  const layout = this.stateLayout[id];
5803
6749
  entries.push({
5804
6750
  name: slot.name,
6751
+ authored: slot.authored,
5805
6752
  type_repr: typeName(this.type(slot.ty), this),
5806
6753
  scalar: shape.scalar,
5807
6754
  array_len: shape.length,
@@ -5809,6 +6756,19 @@ class MirCompiler {
5809
6756
  packed_snapshot_byte_offset: byteOffset,
5810
6757
  physical_state_byte_offset: layout.offset,
5811
6758
  byte_size: layout.size,
6759
+ integer_range: slot.integer_range === undefined || slot.integer_range === null
6760
+ ? null
6761
+ : {
6762
+ min: {
6763
+ type: slot.integer_range.min.type,
6764
+ value: String(slot.integer_range.min.value),
6765
+ },
6766
+ max: {
6767
+ type: slot.integer_range.max.type,
6768
+ value: String(slot.integer_range.max.value),
6769
+ },
6770
+ mode: slot.integer_range.mode,
6771
+ },
5812
6772
  });
5813
6773
  byteOffset += layout.size;
5814
6774
  }