@onda-lang/wasm-compiler 0.8.0 → 0.8.2

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.
@@ -0,0 +1,2297 @@
1
+ import binaryen from "binaryen";
2
+ import { MirCompilerCore } from "./core.js";
3
+ import {
4
+ DELEGATE_RECORD_HEADER_SIZE,
5
+ DELEGATE_BATCH_STORAGE_OFFSET,
6
+ DELEGATE_BATCH_CAPACITY_OFFSET,
7
+ DELEGATE_BATCH_USED_OFFSET,
8
+ DELEGATE_BATCH_RECORD_COUNT_OFFSET,
9
+ DELEGATE_BATCH_OVERFLOW_OFFSET,
10
+ PRINT_RECORD_HEADER_SIZE,
11
+ PRINT_BATCH_STORAGE_OFFSET,
12
+ PRINT_BATCH_CAPACITY_OFFSET,
13
+ PRINT_BATCH_USED_OFFSET,
14
+ PRINT_BATCH_RECORD_COUNT_OFFSET,
15
+ PRINT_BATCH_OVERFLOW_OFFSET,
16
+ INIT_ALL_GLOBAL,
17
+ POINTER_GLOBALS,
18
+ } from "./shared.js";
19
+
20
+ export class MirCompilerLowering extends MirCompilerCore {
21
+ compileBlock(block, context) {
22
+ return this.module.block(
23
+ null,
24
+ block.statements.map((statement) =>
25
+ this.compileStatement(statement, context),
26
+ ),
27
+ );
28
+ }
29
+
30
+ compileStatement(statement, context) {
31
+ const kind = statement.kind?.kind;
32
+ const data = statement.kind?.data;
33
+ switch (kind) {
34
+ case "assign": {
35
+ if (data.value?.kind === "process_frame") {
36
+ const localId =
37
+ data.destination?.base?.kind === "local" &&
38
+ data.destination.projections.length === 0
39
+ ? data.destination.base.data
40
+ : null;
41
+ if (!context.processFrameLocals.has(localId)) {
42
+ this.fail(
43
+ "process_frame must be the unique definition of an unprojected local",
44
+ );
45
+ }
46
+ }
47
+ if (this.type(this.placeTypeId(data.destination, context)).kind === "slice") {
48
+ return this.storeSlicePlace(
49
+ data.destination,
50
+ this.compileSliceRvalue(data.value, context),
51
+ context,
52
+ );
53
+ }
54
+ const scalar = this.placeScalarType(data.destination, context);
55
+ const value = this.compileRvalue(data.value, scalar, context);
56
+ return this.storePlace(data.destination, value, scalar, context);
57
+ }
58
+ case "call":
59
+ return this.compileCall(data, context);
60
+ case "publish_delegate":
61
+ return this.compilePublishDelegate(data, context);
62
+ case "publish_log":
63
+ return this.compilePublishLog(data, context);
64
+ case "output_store":
65
+ return this.compileOutputStore(data, context);
66
+ case "control_output_store":
67
+ return this.compileControlOutputStore(data, context);
68
+ case "if":
69
+ return this.module.if(
70
+ this.compileValue(data.condition, context),
71
+ this.compileBlock(data.then_block, context),
72
+ this.compileBlock(data.else_block, context),
73
+ );
74
+ case "loop": {
75
+ const id = this.nextLabel++;
76
+ const breakLabel = `$onda.break.${id}`;
77
+ const continueLabel = `$onda.loop.${id}`;
78
+ context.breakLabels.push(breakLabel);
79
+ context.continueLabels.push(continueLabel);
80
+ const body = this.compileBlock(data.body, context);
81
+ context.breakLabels.pop();
82
+ context.continueLabels.pop();
83
+ return this.module.block(breakLabel, [
84
+ this.module.loop(
85
+ continueLabel,
86
+ this.module.block(null, [body, this.module.br(continueLabel)]),
87
+ ),
88
+ ]);
89
+ }
90
+ case "break":
91
+ return this.module.br(this.currentLabel(context.breakLabels, "break"));
92
+ case "continue":
93
+ return this.module.br(
94
+ this.currentLabel(context.continueLabels, "continue"),
95
+ );
96
+ case "return": {
97
+ const values = data.values.map((value) =>
98
+ this.compileValue(value, context),
99
+ );
100
+ return this.module.return(
101
+ values.length > 1 ? this.module.tuple.make(values) : values[0],
102
+ );
103
+ }
104
+ case "buffer_store":
105
+ return this.compileBufferStore(data, context);
106
+ case "buffer_param_store":
107
+ return this.compileBufferParamStore(data, context);
108
+ case "slice_store":
109
+ return this.compileSliceStore(data, context);
110
+ case "slice_fill":
111
+ return this.compileSliceFill(statement, data, context);
112
+ case "slice_copy":
113
+ return this.compileSliceCopy(statement, data, context);
114
+ default:
115
+ this.fail(`unknown MIR statement '${String(kind)}'`);
116
+ }
117
+ }
118
+
119
+ compileCall(data, context) {
120
+ this.requireFunctionId(data.function, "call target");
121
+ const target = this.mir.functions[data.function];
122
+ if (data.args.length !== target.params.length) {
123
+ this.fail(`call to '${target.name}' has the wrong number of arguments`);
124
+ }
125
+ const args = data.args.flatMap((argument, index) => {
126
+ const parameterType = this.type(target.params[index].ty);
127
+ if (parameterType.kind === "scalar") {
128
+ const passingMode = this.parameterPassingMode(data.function, index);
129
+ if (passingMode === "value") {
130
+ if (target.params[index].mode !== "value") {
131
+ if (argument.kind !== "place") {
132
+ this.fail(
133
+ `promoted scalar reference argument ${index} of '${target.name}' is not a place`,
134
+ );
135
+ }
136
+ return [this.loadPlace(argument.data, context)];
137
+ }
138
+ if (argument.kind !== "value") {
139
+ this.fail(`scalar call argument ${index} of '${target.name}' is not a value`);
140
+ }
141
+ return [this.compileValue(argument.data, context)];
142
+ }
143
+ if (!["place", "slice_element"].includes(argument.kind)) {
144
+ this.fail(
145
+ `reference call argument ${index} of '${target.name}' is not addressable`,
146
+ );
147
+ }
148
+ return [
149
+ argument.kind === "place"
150
+ ? this.placeAddress(argument.data, context)
151
+ : this.compileSliceAddress(
152
+ argument.data.slice,
153
+ argument.data.index,
154
+ argument.data.bounds,
155
+ context,
156
+ target.params[index].mode === "read_write_reference",
157
+ ),
158
+ ];
159
+ }
160
+ if (parameterType.kind === "slice") {
161
+ if (argument.kind !== "value") {
162
+ this.fail(`slice call argument ${index} of '${target.name}' is not a value`);
163
+ }
164
+ return this.compileSliceValue(argument.data, context);
165
+ }
166
+ if (parameterType.kind === "array") {
167
+ if (
168
+ target.params[index].mode === "value" ||
169
+ !["place", "array_window", "slice_window"].includes(argument.kind)
170
+ ) {
171
+ this.fail(`array reference argument ${index} of '${target.name}' is invalid`);
172
+ }
173
+ if (argument.kind === "place") {
174
+ return [this.placeAddress(argument.data, context)];
175
+ }
176
+ if (argument.kind === "array_window") {
177
+ return [
178
+ this.compileArrayWindowAddress(
179
+ argument.data,
180
+ parameterType,
181
+ context,
182
+ ),
183
+ ];
184
+ }
185
+ return [
186
+ this.compileSliceWindowAddress(
187
+ argument.data,
188
+ parameterType,
189
+ context,
190
+ target.params[index].mode === "read_write_reference",
191
+ ),
192
+ ];
193
+ }
194
+ if (parameterType.kind === "buffer") {
195
+ if (argument.kind === "buffer") {
196
+ return this.compileInterfaceBufferValue(argument.data, context);
197
+ }
198
+ if (argument.kind === "buffer_param") {
199
+ return this.loadBufferParamValue(argument.data, context);
200
+ }
201
+ if (argument.kind === "place") {
202
+ return this.loadBufferPlace(argument.data, context);
203
+ }
204
+ this.fail(`buffer call argument ${index} of '${target.name}' is invalid`);
205
+ }
206
+ if (parameterType.kind === "buffer_span") {
207
+ if (argument.kind !== "buffer_span") {
208
+ this.fail(`buffer span call argument ${index} of '${target.name}' is invalid`);
209
+ }
210
+ return this.compileBufferSpanValue(argument.data, parameterType, context);
211
+ }
212
+ this.fail(
213
+ `call argument ${index} of '${target.name}' has unsupported type '${parameterType.kind}'`,
214
+ );
215
+ });
216
+ if (data.results.length !== target.results.length) {
217
+ this.fail(`call result arity for '${target.name}' does not match its signature`);
218
+ }
219
+ const resultScalars = target.results.map((result, resultId) =>
220
+ this.requireScalarType(result, `result ${resultId} of '${target.name}'`),
221
+ );
222
+ const resultType = this.wasmResultType(resultScalars);
223
+ const call = this.module.call(this.functionNames[data.function], args, resultType);
224
+ const localReferenceSync = data.args.flatMap((argument, index) => {
225
+ const parameter = target.params[index];
226
+ if (
227
+ this.parameterPassingMode(data.function, index) === "value"
228
+ || argument.kind !== "place"
229
+ || argument.data.base.kind !== "local"
230
+ || argument.data.projections.length !== 0
231
+ ) {
232
+ return [];
233
+ }
234
+ const layout =
235
+ this.localScalarRefLayout[context.functionId]?.[argument.data.base.data];
236
+ if (!layout) return [];
237
+ return [{
238
+ localId: argument.data.base.data,
239
+ address: layout.address,
240
+ scalar: layout.scalar,
241
+ writeBack: parameter.mode === "read_write_reference",
242
+ }];
243
+ });
244
+ const beforeCall = localReferenceSync.map((sync) =>
245
+ this.storeScalar(
246
+ sync.scalar,
247
+ this.module.i32.const(sync.address),
248
+ this.module.local.get(
249
+ this.localIndex(sync.localId, context),
250
+ this.wasmType(sync.scalar),
251
+ ),
252
+ ),
253
+ );
254
+ const afterCall = localReferenceSync
255
+ .filter((sync) => sync.writeBack)
256
+ .map((sync) =>
257
+ this.module.local.set(
258
+ this.localIndex(sync.localId, context),
259
+ this.loadScalar(sync.scalar, this.module.i32.const(sync.address)),
260
+ ),
261
+ );
262
+ const resultSpill = context.callResultLocals.get(data);
263
+ if (localReferenceSync.length > 0 && data.results.length > 0) {
264
+ if (!resultSpill) {
265
+ this.fail(`internal result spill is missing for call to '${target.name}'`);
266
+ }
267
+ const spilledValue = () =>
268
+ this.module.local.get(resultSpill.index, resultSpill.type);
269
+ const assignResults = data.results.length === 1
270
+ ? [
271
+ this.module.local.set(
272
+ this.localIndex(data.results[0], context),
273
+ spilledValue(),
274
+ ),
275
+ ]
276
+ : data.results.map((localId, index) =>
277
+ this.module.local.set(
278
+ this.localIndex(localId, context),
279
+ this.module.tuple.extract(spilledValue(), index),
280
+ ),
281
+ );
282
+ return this.module.block(null, [
283
+ ...beforeCall,
284
+ this.module.local.set(resultSpill.index, call),
285
+ ...afterCall,
286
+ ...assignResults,
287
+ ...this.propagateRuntimeFailure(data.function, context),
288
+ ]);
289
+ }
290
+ let compiledCall;
291
+ if (data.results.length === 0) {
292
+ compiledCall = call;
293
+ } else if (data.results.length === 1) {
294
+ compiledCall = this.module.local.set(
295
+ this.localIndex(data.results[0], context),
296
+ call,
297
+ );
298
+ } else {
299
+ const tupleLocal = context.callResultLocals.get(data);
300
+ if (!tupleLocal) {
301
+ this.fail(`internal tuple spill is missing for call to '${target.name}'`);
302
+ }
303
+ const tupleValue = () =>
304
+ this.module.local.get(tupleLocal.index, tupleLocal.type);
305
+ compiledCall = this.module.block(null, [
306
+ this.module.local.set(tupleLocal.index, call),
307
+ ...data.results.map((localId, index) =>
308
+ this.module.local.set(
309
+ this.localIndex(localId, context),
310
+ this.module.tuple.extract(tupleValue(), index),
311
+ ),
312
+ ),
313
+ ]);
314
+ }
315
+ if (localReferenceSync.length === 0) {
316
+ const propagation = this.propagateRuntimeFailure(data.function, context);
317
+ return propagation.length === 0
318
+ ? compiledCall
319
+ : this.module.block(null, [compiledCall, ...propagation]);
320
+ }
321
+ return this.module.block(null, [
322
+ ...beforeCall,
323
+ compiledCall,
324
+ ...afterCall,
325
+ ...this.propagateRuntimeFailure(data.function, context),
326
+ ]);
327
+ }
328
+
329
+ compilePublishDelegate(data, context) {
330
+ const delegate = this.mir.interface.delegates[data.delegate];
331
+ const layout = this.delegateLayout[data.delegate];
332
+ if (!delegate || !layout || data.args?.length !== delegate.params.length) {
333
+ this.fail(`delegate id ${String(data.delegate)} has an invalid publication payload`);
334
+ }
335
+ if (context.function.attributes.runtime_context !== true) {
336
+ this.fail(`function '${context.function.name}' publishes without runtime context`);
337
+ }
338
+
339
+ const payloadBytes = this.allocateGeneratedLocal(
340
+ context,
341
+ "i32",
342
+ `delegate.${data.delegate}.payload_bytes`,
343
+ );
344
+ const oversized = this.allocateGeneratedLocal(
345
+ context,
346
+ "i32",
347
+ `delegate.${data.delegate}.oversized`,
348
+ );
349
+ const record = this.allocateGeneratedLocal(
350
+ context,
351
+ "i32",
352
+ `delegate.${data.delegate}.record`,
353
+ );
354
+ const cursor = this.allocateGeneratedLocal(
355
+ context,
356
+ "i32",
357
+ `delegate.${data.delegate}.cursor`,
358
+ );
359
+ const counter = this.allocateGeneratedLocal(
360
+ context,
361
+ "i32",
362
+ `delegate.${data.delegate}.copy_index`,
363
+ );
364
+ const payload = () => this.module.local.get(payloadBytes, binaryen.i32);
365
+ const tooLarge = () => this.module.local.get(oversized, binaryen.i32);
366
+ const batch = () =>
367
+ this.module.global.get(POINTER_GLOBALS.delegateBatch, binaryen.i32);
368
+ const validationStatements = [];
369
+ const collectionStatements = [
370
+ this.module.local.set(
371
+ payloadBytes,
372
+ this.module.i32.const(layout.minimumByteLength),
373
+ ),
374
+ this.module.local.set(
375
+ oversized,
376
+ this.module.i32.const(
377
+ layout.minimumByteLength > 0xffff_ffff - DELEGATE_RECORD_HEADER_SIZE
378
+ ? 1
379
+ : 0,
380
+ ),
381
+ ),
382
+ ];
383
+
384
+ for (const [paramId, param] of delegate.params.entries()) {
385
+ const type = this.type(param.ty);
386
+ const argument = data.args[paramId];
387
+ if (argument?.kind !== "value") {
388
+ this.fail(`delegate '${delegate.name}' argument ${paramId} is not a value`);
389
+ }
390
+ if (type.kind !== "slice") continue;
391
+ const element = type.data.element;
392
+ const elementSize = this.scalarSize(element);
393
+ const length = () => this.compileSliceValue(argument.data, context)[2];
394
+ const delta = () =>
395
+ this.module.i32.mul(length(), this.module.i32.const(elementSize));
396
+ const next = () => this.module.i32.add(payload(), delta());
397
+ collectionStatements.push(
398
+ this.module.local.set(
399
+ oversized,
400
+ this.module.i32.or(
401
+ tooLarge(),
402
+ this.module.i32.or(
403
+ this.module.i32.gt_u(
404
+ length(),
405
+ this.module.i32.const(Math.floor(0xffff_ffff / elementSize)),
406
+ ),
407
+ this.module.i32.lt_u(next(), payload()),
408
+ ),
409
+ ),
410
+ ),
411
+ this.module.local.set(payloadBytes, next()),
412
+ );
413
+ }
414
+ for (const [paramId, param] of delegate.params.entries()) {
415
+ const type = this.type(param.ty);
416
+ if (type.kind !== "array") continue;
417
+ const argument = data.args[paramId];
418
+ const sliceLength = () => this.compileSliceValue(argument.data, context)[2];
419
+ validationStatements.push(
420
+ this.module.if(
421
+ this.module.i32.ne(
422
+ sliceLength(),
423
+ this.module.i32.const(type.data.len),
424
+ ),
425
+ this.raiseRuntimeFailure(context),
426
+ ),
427
+ );
428
+ }
429
+ collectionStatements.push(
430
+ this.module.local.set(
431
+ oversized,
432
+ this.module.i32.or(
433
+ tooLarge(),
434
+ this.module.i32.gt_u(
435
+ payload(),
436
+ this.module.i32.const(0xffff_ffff - DELEGATE_RECORD_HEADER_SIZE),
437
+ ),
438
+ ),
439
+ ),
440
+ this.compileDelegateBatchAppend(
441
+ data.delegate,
442
+ delegate,
443
+ data.args,
444
+ payload,
445
+ tooLarge,
446
+ record,
447
+ cursor,
448
+ counter,
449
+ context,
450
+ ),
451
+ );
452
+ return this.module.block(null, [
453
+ ...validationStatements,
454
+ this.module.if(
455
+ this.module.i32.ne(batch(), this.module.i32.const(0)),
456
+ this.module.block(null, collectionStatements),
457
+ ),
458
+ ]);
459
+ }
460
+
461
+ compilePublishLog(data, context) {
462
+ const site = this.mir.log_sites?.[data.site];
463
+ const args = data.arguments;
464
+ if (!site || !Array.isArray(args) || args.length !== site.argument_types?.length) {
465
+ this.fail(`log site id ${String(data.site)} has an invalid publication payload`);
466
+ }
467
+ if (context.function.attributes.runtime_context !== true) {
468
+ this.fail(`function '${context.function.name}' prints without runtime context`);
469
+ }
470
+
471
+ const payloadSize = site.payload_size;
472
+ const calculatedSize = site.argument_types.reduce(
473
+ (size, scalar) => size + this.scalarSize(scalar),
474
+ 0,
475
+ );
476
+ if (!Number.isInteger(payloadSize) || payloadSize !== calculatedSize) {
477
+ this.fail(`log site id ${data.site} has an invalid payload size`);
478
+ }
479
+
480
+ const storageLocal = this.allocateGeneratedLocal(
481
+ context,
482
+ "i32",
483
+ `log.${data.site}.storage`,
484
+ );
485
+ const capacityLocal = this.allocateGeneratedLocal(
486
+ context,
487
+ "i32",
488
+ `log.${data.site}.capacity`,
489
+ );
490
+ const usedLocal = this.allocateGeneratedLocal(
491
+ context,
492
+ "i32",
493
+ `log.${data.site}.used`,
494
+ );
495
+ const recordLocal = this.allocateGeneratedLocal(
496
+ context,
497
+ "i32",
498
+ `log.${data.site}.record`,
499
+ );
500
+ const cursorLocal = this.allocateGeneratedLocal(
501
+ context,
502
+ "i32",
503
+ `log.${data.site}.cursor`,
504
+ );
505
+ const sequenceLocal = this.allocateGeneratedLocal(
506
+ context,
507
+ "i32",
508
+ `log.${data.site}.sequence`,
509
+ );
510
+ const batch = () =>
511
+ this.module.global.get(POINTER_GLOBALS.printBatch, binaryen.i32);
512
+ const field = (offset) =>
513
+ this.module.i32.add(batch(), this.module.i32.const(offset));
514
+ const local = (id) => this.module.local.get(id, binaryen.i32);
515
+ const requiredSize = payloadSize + PRINT_RECORD_HEADER_SIZE;
516
+ const overflowAddress = () => field(PRINT_BATCH_OVERFLOW_OFFSET);
517
+ const overflow = () => this.module.i32.load(0, 4, overflowAddress());
518
+ const drop = this.module.i32.store(
519
+ 0,
520
+ 4,
521
+ overflowAddress(),
522
+ this.module.select(
523
+ this.module.i32.eq(overflow(), this.module.i32.const(-1)),
524
+ overflow(),
525
+ this.module.i32.add(overflow(), this.module.i32.const(1)),
526
+ ),
527
+ );
528
+ const fits = this.module.i32.and(
529
+ this.module.i32.le_u(local(usedLocal), local(capacityLocal)),
530
+ this.module.i32.le_u(
531
+ this.module.i32.const(requiredSize),
532
+ this.module.i32.sub(local(capacityLocal), local(usedLocal)),
533
+ ),
534
+ );
535
+ const record = () => local(recordLocal);
536
+ const cursor = () => local(cursorLocal);
537
+ const write = [
538
+ this.module.local.set(
539
+ recordLocal,
540
+ this.module.i32.add(local(storageLocal), local(usedLocal)),
541
+ ),
542
+ this.module.i32.store(0, 1, record(), this.module.i32.const(data.site)),
543
+ this.module.i32.store(4, 1, record(), this.module.i32.const(payloadSize)),
544
+ this.module.i32.store(8, 1, record(), local(sequenceLocal)),
545
+ this.module.local.set(
546
+ cursorLocal,
547
+ this.module.i32.add(record(), this.module.i32.const(PRINT_RECORD_HEADER_SIZE)),
548
+ ),
549
+ ];
550
+ for (const [index, scalar] of site.argument_types.entries()) {
551
+ write.push(
552
+ this.storePackedScalar(
553
+ scalar,
554
+ cursor(),
555
+ this.compileValue(args[index], context),
556
+ ),
557
+ this.module.local.set(
558
+ cursorLocal,
559
+ this.module.i32.add(cursor(), this.module.i32.const(this.scalarSize(scalar))),
560
+ ),
561
+ );
562
+ }
563
+ const usedAddress = () => field(PRINT_BATCH_USED_OFFSET);
564
+ const countAddress = () => field(PRINT_BATCH_RECORD_COUNT_OFFSET);
565
+ write.push(
566
+ this.module.i32.store(
567
+ 0,
568
+ 4,
569
+ usedAddress(),
570
+ this.module.i32.add(local(usedLocal), this.module.i32.const(requiredSize)),
571
+ ),
572
+ this.module.i32.store(
573
+ 0,
574
+ 4,
575
+ countAddress(),
576
+ this.module.i32.add(
577
+ this.module.i32.load(0, 4, countAddress()),
578
+ this.module.i32.const(1),
579
+ ),
580
+ ),
581
+ );
582
+ return this.module.if(
583
+ this.module.i32.ne(batch(), this.module.i32.const(0)),
584
+ this.module.block(null, [
585
+ this.advanceOutputSequence(sequenceLocal),
586
+ this.module.local.set(
587
+ storageLocal,
588
+ this.module.i32.load(0, 4, field(PRINT_BATCH_STORAGE_OFFSET)),
589
+ ),
590
+ this.module.if(
591
+ this.module.i32.ne(local(storageLocal), this.module.i32.const(0)),
592
+ this.module.block(null, [
593
+ this.module.local.set(
594
+ capacityLocal,
595
+ this.module.i32.load(0, 4, field(PRINT_BATCH_CAPACITY_OFFSET)),
596
+ ),
597
+ this.module.local.set(
598
+ usedLocal,
599
+ this.module.i32.load(0, 4, field(PRINT_BATCH_USED_OFFSET)),
600
+ ),
601
+ this.module.if(fits, this.module.block(null, write), drop),
602
+ ]),
603
+ ),
604
+ ]),
605
+ );
606
+ }
607
+
608
+ compileDelegateBatchAppend(
609
+ delegateId,
610
+ delegate,
611
+ args,
612
+ payload,
613
+ tooLarge,
614
+ recordLocal,
615
+ cursorLocal,
616
+ counterLocal,
617
+ context,
618
+ ) {
619
+ const batch = () =>
620
+ this.module.global.get(POINTER_GLOBALS.delegateBatch, binaryen.i32);
621
+ const field = (offset) =>
622
+ this.module.i32.add(batch(), this.module.i32.const(offset));
623
+ const storage = this.allocateGeneratedLocal(
624
+ context,
625
+ "i32",
626
+ `delegate.${delegateId}.storage`,
627
+ );
628
+ const capacity = this.allocateGeneratedLocal(
629
+ context,
630
+ "i32",
631
+ `delegate.${delegateId}.capacity`,
632
+ );
633
+ const used = this.allocateGeneratedLocal(
634
+ context,
635
+ "i32",
636
+ `delegate.${delegateId}.used`,
637
+ );
638
+ const required = this.allocateGeneratedLocal(
639
+ context,
640
+ "i32",
641
+ `delegate.${delegateId}.required`,
642
+ );
643
+ const sequence = this.allocateGeneratedLocal(
644
+ context,
645
+ "i32",
646
+ `delegate.${delegateId}.sequence`,
647
+ );
648
+ const local = (id) => this.module.local.get(id, binaryen.i32);
649
+ const write = this.compileDelegateRecord(
650
+ delegateId,
651
+ delegate,
652
+ args,
653
+ payload,
654
+ () => local(storage),
655
+ () => local(used),
656
+ recordLocal,
657
+ cursorLocal,
658
+ counterLocal,
659
+ sequence,
660
+ context,
661
+ );
662
+ const overflowAddress = () => field(DELEGATE_BATCH_OVERFLOW_OFFSET);
663
+ const overflow = () => this.module.i32.load(0, 4, overflowAddress());
664
+ const drop = this.module.i32.store(
665
+ 0,
666
+ 4,
667
+ overflowAddress(),
668
+ this.module.select(
669
+ this.module.i32.eq(overflow(), this.module.i32.const(-1)),
670
+ overflow(),
671
+ this.module.i32.add(overflow(), this.module.i32.const(1)),
672
+ ),
673
+ );
674
+ const fit = this.module.i32.and(
675
+ this.module.i32.eq(tooLarge(), this.module.i32.const(0)),
676
+ this.module.i32.and(
677
+ this.module.i32.le_u(local(used), local(capacity)),
678
+ this.module.i32.le_u(
679
+ local(required),
680
+ this.module.i32.sub(local(capacity), local(used)),
681
+ ),
682
+ ),
683
+ );
684
+ return this.module.block(null, [
685
+ this.advanceOutputSequence(sequence),
686
+ this.module.local.set(
687
+ storage,
688
+ this.module.i32.load(0, 4, field(DELEGATE_BATCH_STORAGE_OFFSET)),
689
+ ),
690
+ this.module.if(
691
+ this.module.i32.ne(local(storage), this.module.i32.const(0)),
692
+ this.module.block(null, [
693
+ this.module.local.set(
694
+ capacity,
695
+ this.module.i32.load(0, 4, field(DELEGATE_BATCH_CAPACITY_OFFSET)),
696
+ ),
697
+ this.module.local.set(
698
+ used,
699
+ this.module.i32.load(0, 4, field(DELEGATE_BATCH_USED_OFFSET)),
700
+ ),
701
+ this.module.local.set(
702
+ required,
703
+ this.module.i32.add(
704
+ payload(),
705
+ this.module.i32.const(DELEGATE_RECORD_HEADER_SIZE),
706
+ ),
707
+ ),
708
+ this.module.if(fit, write, drop),
709
+ ]),
710
+ ),
711
+ ]);
712
+ }
713
+
714
+ compileDelegateRecord(
715
+ delegateId,
716
+ delegate,
717
+ args,
718
+ payload,
719
+ storage,
720
+ used,
721
+ recordLocal,
722
+ cursorLocal,
723
+ counterLocal,
724
+ sequenceLocal,
725
+ context,
726
+ ) {
727
+ const batch = () =>
728
+ this.module.global.get(POINTER_GLOBALS.delegateBatch, binaryen.i32);
729
+ const record = () => this.module.local.get(recordLocal, binaryen.i32);
730
+ const cursor = () => this.module.local.get(cursorLocal, binaryen.i32);
731
+ const statements = [];
732
+ statements.push(
733
+ this.module.local.set(recordLocal, this.module.i32.add(storage(), used())),
734
+ this.module.i32.store(0, 1, record(), this.module.i32.const(delegateId)),
735
+ this.module.i32.store(4, 1, record(), payload()),
736
+ this.module.i32.store(
737
+ 8,
738
+ 1,
739
+ record(),
740
+ this.module.local.get(sequenceLocal, binaryen.i32),
741
+ ),
742
+ this.module.local.set(
743
+ cursorLocal,
744
+ this.module.i32.add(
745
+ record(),
746
+ this.module.i32.const(DELEGATE_RECORD_HEADER_SIZE),
747
+ ),
748
+ ),
749
+ );
750
+ for (const [paramId, param] of delegate.params.entries()) {
751
+ const type = this.type(param.ty);
752
+ const argument = args[paramId];
753
+ if (type.kind === "scalar") {
754
+ statements.push(
755
+ this.storePackedScalar(
756
+ type.data,
757
+ cursor(),
758
+ this.compileValue(argument.data, context),
759
+ ),
760
+ this.module.local.set(
761
+ cursorLocal,
762
+ this.module.i32.add(
763
+ cursor(),
764
+ this.module.i32.const(this.scalarSize(type.data)),
765
+ ),
766
+ ),
767
+ );
768
+ continue;
769
+ }
770
+ const element = type.kind === "slice"
771
+ ? type.data.element
772
+ : this.requireScalarType(
773
+ type.data.element,
774
+ `delegate '${delegate.name}' aggregate parameter ${paramId}`,
775
+ );
776
+ const slice = () => this.compileSliceValue(argument.data, context);
777
+ const count = type.kind === "array"
778
+ ? () => this.module.i32.const(type.data.len)
779
+ : () => slice()[2];
780
+ if (type.kind === "slice") {
781
+ statements.push(
782
+ this.module.i32.store(0, 1, cursor(), count()),
783
+ this.module.local.set(
784
+ cursorLocal,
785
+ this.module.i32.add(cursor(), this.module.i32.const(4)),
786
+ ),
787
+ );
788
+ }
789
+ statements.push(
790
+ this.compilePackedSliceCopy(
791
+ slice,
792
+ count,
793
+ element,
794
+ cursor,
795
+ counterLocal,
796
+ ),
797
+ this.module.local.set(
798
+ cursorLocal,
799
+ this.module.i32.add(
800
+ cursor(),
801
+ this.module.i32.mul(
802
+ count(),
803
+ this.module.i32.const(this.scalarSize(element)),
804
+ ),
805
+ ),
806
+ ),
807
+ );
808
+ }
809
+ const usedAddress = () =>
810
+ this.module.i32.add(
811
+ batch(),
812
+ this.module.i32.const(DELEGATE_BATCH_USED_OFFSET),
813
+ );
814
+ const countAddress = () =>
815
+ this.module.i32.add(
816
+ batch(),
817
+ this.module.i32.const(DELEGATE_BATCH_RECORD_COUNT_OFFSET),
818
+ );
819
+ statements.push(
820
+ this.module.i32.store(
821
+ 0,
822
+ 4,
823
+ usedAddress(),
824
+ this.module.i32.add(
825
+ used(),
826
+ this.module.i32.add(
827
+ payload(),
828
+ this.module.i32.const(DELEGATE_RECORD_HEADER_SIZE),
829
+ ),
830
+ ),
831
+ ),
832
+ this.module.i32.store(
833
+ 0,
834
+ 4,
835
+ countAddress(),
836
+ this.module.i32.add(
837
+ this.module.i32.load(0, 4, countAddress()),
838
+ this.module.i32.const(1),
839
+ ),
840
+ ),
841
+ );
842
+ return this.module.block(null, statements);
843
+ }
844
+
845
+ compilePackedSliceCopy(slice, count, scalar, destination, counterLocal) {
846
+ const loopLabel = `$onda.delegate.copy.${this.nextLabel++}`;
847
+ const counter = () => this.module.local.get(counterLocal, binaryen.i32);
848
+ const sourceAddress = () =>
849
+ this.module.i32.add(
850
+ slice()[0],
851
+ this.module.i32.mul(counter(), slice()[3]),
852
+ );
853
+ const destinationAddress = () =>
854
+ this.module.i32.add(
855
+ destination(),
856
+ this.module.i32.mul(
857
+ counter(),
858
+ this.module.i32.const(this.scalarSize(scalar)),
859
+ ),
860
+ );
861
+ return this.module.block(null, [
862
+ this.module.local.set(counterLocal, this.module.i32.const(0)),
863
+ this.module.loop(
864
+ loopLabel,
865
+ this.module.if(
866
+ this.module.i32.lt_u(counter(), count()),
867
+ this.module.block(null, [
868
+ this.storePackedScalar(
869
+ scalar,
870
+ destinationAddress(),
871
+ this.loadScalar(scalar, sourceAddress()),
872
+ ),
873
+ this.module.local.set(
874
+ counterLocal,
875
+ this.module.i32.add(counter(), this.module.i32.const(1)),
876
+ ),
877
+ this.module.br(loopLabel),
878
+ ]),
879
+ ),
880
+ ),
881
+ ]);
882
+ }
883
+
884
+ compileOutputStore(data, context) {
885
+ this.requireProcessFrame(data.frame, context, "audio output store");
886
+ const port = this.outputLayout[data.output];
887
+ if (!port) {
888
+ this.fail(`output id ${data.output} is out of range`);
889
+ }
890
+ const channelPointer = this.audioChannelPointer(
891
+ POINTER_GLOBALS.outputs,
892
+ port,
893
+ data.element,
894
+ data.bounds,
895
+ context,
896
+ );
897
+ const sampleAddress = this.module.i32.add(
898
+ channelPointer,
899
+ this.module.i32.mul(
900
+ this.compileValue(data.frame, context),
901
+ this.module.i32.const(port.size),
902
+ ),
903
+ );
904
+ return this.storeScalar(
905
+ port.scalar,
906
+ sampleAddress,
907
+ this.compileValue(data.value, context),
908
+ );
909
+ }
910
+
911
+ compileControlOutputStore(data, context) {
912
+ const output = this.mir.interface.control_outputs[data.output];
913
+ const layout = this.controlOutputLayout[data.output];
914
+ if (!output || !layout) {
915
+ this.fail(`control output id ${data.output} is out of range`);
916
+ }
917
+ const flattened = this.flattenPortType(this.type(output.ty));
918
+ let elementOffset = this.module.i32.const(0);
919
+ if (this.type(output.ty).kind !== "array") {
920
+ if (data.element !== null) {
921
+ this.fail("scalar control output unexpectedly has an element index");
922
+ }
923
+ } else {
924
+ if (data.element === null) {
925
+ this.fail("array control output is missing its element index");
926
+ }
927
+ const index = this.compileBoundedIndex(
928
+ data.element,
929
+ flattened.channels,
930
+ data.bounds,
931
+ context,
932
+ );
933
+ elementOffset = this.module.i32.mul(
934
+ index,
935
+ this.module.i32.const(layout.size / flattened.channels),
936
+ );
937
+ }
938
+ const address = this.module.i32.add(
939
+ this.module.i32.add(
940
+ this.module.global.get(POINTER_GLOBALS.state, binaryen.i32),
941
+ this.module.i32.const(layout.offset),
942
+ ),
943
+ elementOffset,
944
+ );
945
+ return this.storeScalar(
946
+ flattened.scalar,
947
+ address,
948
+ this.compileValue(data.value, context),
949
+ );
950
+ }
951
+
952
+ compileBufferStore(data, context) {
953
+ const buffer = this.requireBufferRef(data.buffer);
954
+ if (buffer.access !== "read_write") {
955
+ this.fail(`buffer '${buffer.name}' is read-only`);
956
+ }
957
+ return this.storeScalar(
958
+ buffer.element,
959
+ this.compileBufferAddress(data, context, true),
960
+ this.compileValue(data.value, context),
961
+ );
962
+ }
963
+
964
+ compileBufferParamStore(data, context) {
965
+ const type = this.bufferParamType(data.parameter, context);
966
+ if (type.data.access !== "read_write") {
967
+ this.fail(`buffer parameter ${data.parameter} is read-only`);
968
+ }
969
+ return this.storeScalar(
970
+ type.data.element,
971
+ this.compileBufferParamAddress(data, context, true),
972
+ this.compileValue(data.value, context),
973
+ );
974
+ }
975
+
976
+ compileRvalue(rvalue, expectedScalar, context) {
977
+ const kind = rvalue.kind;
978
+ const data = rvalue.data;
979
+ switch (kind) {
980
+ case "use":
981
+ return this.compileValue(data, context);
982
+ case "load":
983
+ return this.loadPlace(data, context);
984
+ case "unary":
985
+ return this.compileUnary(
986
+ data.op,
987
+ this.valueScalarType(data.operand, context),
988
+ this.compileValue(data.operand, context),
989
+ );
990
+ case "binary": {
991
+ const scalar = this.valueScalarType(data.lhs, context);
992
+ return this.compileBinary(
993
+ data.op,
994
+ scalar,
995
+ () => this.compileValue(data.lhs, context),
996
+ () => this.compileValue(data.rhs, context),
997
+ context,
998
+ );
999
+ }
1000
+ case "compare": {
1001
+ const scalar = this.valueScalarType(data.lhs, context);
1002
+ return this.compileCompare(
1003
+ data.op,
1004
+ scalar,
1005
+ this.compileValue(data.lhs, context),
1006
+ this.compileValue(data.rhs, context),
1007
+ );
1008
+ }
1009
+ case "cast":
1010
+ return this.compileCast(
1011
+ this.valueScalarType(data.value, context),
1012
+ data.to,
1013
+ this.compileValue(data.value, context),
1014
+ );
1015
+ case "intrinsic":
1016
+ return this.compileIntrinsic(data, expectedScalar, context);
1017
+ case "init_all":
1018
+ if (context.function.kind?.kind !== "init") {
1019
+ this.fail("init_all is only valid in the init entry point");
1020
+ }
1021
+ return this.module.global.get(INIT_ALL_GLOBAL, binaryen.i32);
1022
+ case "process_frame":
1023
+ return this.compileProcessFrame(data, context);
1024
+ case "input_load":
1025
+ return this.compileInputLoad(data, context);
1026
+ case "output_load":
1027
+ return this.compileOutputLoad(data, context);
1028
+ case "const_data_load":
1029
+ return this.compileConstDataLoad(data, context);
1030
+ case "buffer_load":
1031
+ return this.compileBufferLoad(data, context);
1032
+ case "buffer_param_load":
1033
+ return this.compileBufferParamLoad(data, context);
1034
+ case "buffer_len":
1035
+ return this.compileBufferLen(data, context);
1036
+ case "buffer_param_len":
1037
+ return this.compileBufferParamLen(data, context);
1038
+ case "buffer_channels":
1039
+ return this.compileBufferChannels(data, context);
1040
+ case "buffer_param_channels":
1041
+ return this.compileBufferParamChannels(data, context);
1042
+ case "buffer_sample_rate":
1043
+ return this.loadBufferTableValue(
1044
+ POINTER_GLOBALS.bufferSampleRates,
1045
+ data,
1046
+ "f32",
1047
+ context,
1048
+ );
1049
+ case "buffer_param_sample_rate":
1050
+ return this.loadBufferParamComponent(data, 4, "f32", context);
1051
+ case "buffer_is_bound":
1052
+ return this.compileBufferIsBound(data, context);
1053
+ case "buffer_param_is_bound":
1054
+ return this.loadBufferParamComponent(data, 5, "i32", context);
1055
+ case "slice_len":
1056
+ return this.compileSliceValue(data, context)[2];
1057
+ case "slice_load":
1058
+ return this.compileSliceLoad(data, context);
1059
+ case "make_slice":
1060
+ this.fail("make_slice must be assigned to a slice-typed destination");
1061
+ break;
1062
+ default:
1063
+ this.fail(`unknown MIR rvalue '${String(kind)}'`);
1064
+ }
1065
+ }
1066
+
1067
+ compileSliceRvalue(rvalue, context) {
1068
+ switch (rvalue.kind) {
1069
+ case "use":
1070
+ return this.compileSliceValue(rvalue.data, context);
1071
+ case "load":
1072
+ return this.loadSlicePlace(rvalue.data, context);
1073
+ case "make_slice":
1074
+ return this.compileMakeSlice(rvalue.data, context);
1075
+ default:
1076
+ this.fail(`rvalue '${String(rvalue.kind)}' does not produce a slice`);
1077
+ }
1078
+ }
1079
+
1080
+ compileInputLoad(data, context) {
1081
+ this.requireProcessFrame(data.frame, context, "audio input load");
1082
+ const port = this.inputLayout[data.input];
1083
+ if (!port) {
1084
+ this.fail(`input id ${data.input} is out of range`);
1085
+ }
1086
+ const channelPointer = this.audioChannelPointer(
1087
+ POINTER_GLOBALS.inputs,
1088
+ port,
1089
+ data.element,
1090
+ data.bounds,
1091
+ context,
1092
+ );
1093
+ const sampleAddress = this.module.i32.add(
1094
+ channelPointer,
1095
+ this.module.i32.mul(
1096
+ this.compileValue(data.frame, context),
1097
+ this.module.i32.const(port.size),
1098
+ ),
1099
+ );
1100
+ return this.loadScalar(port.scalar, sampleAddress);
1101
+ }
1102
+
1103
+ compileProcessFrame(data, context) {
1104
+ if (context.function.kind?.kind !== "process") {
1105
+ this.fail("process_frame is only valid in the process entry point");
1106
+ }
1107
+ const offset = () => this.compileValue(data.offset, context);
1108
+ const startFrame = () =>
1109
+ this.module.local.get(context.paramLayouts[0].index, binaryen.i32);
1110
+ const frames = () =>
1111
+ this.module.local.get(context.paramLayouts[1].index, binaryen.i32);
1112
+ const invalid = this.module.i32.ge_u(offset(), frames());
1113
+ return this.module.if(
1114
+ invalid,
1115
+ this.raiseRuntimeFailure(context),
1116
+ this.module.i32.add(startFrame(), offset()),
1117
+ );
1118
+ }
1119
+
1120
+ requireProcessFrame(value, context, operation) {
1121
+ if (
1122
+ context.function.kind?.kind !== "process" ||
1123
+ value?.kind !== "local" ||
1124
+ !context.processFrameLocals.has(value.data)
1125
+ ) {
1126
+ this.fail(`${operation} frame must come directly from process_frame`);
1127
+ }
1128
+ }
1129
+
1130
+ compileOutputLoad(data, context) {
1131
+ this.requireProcessFrame(data.frame, context, "audio output load");
1132
+ const port = this.outputLayout[data.output];
1133
+ if (!port) {
1134
+ this.fail(`output id ${data.output} is out of range`);
1135
+ }
1136
+ const channelPointer = this.audioChannelPointer(
1137
+ POINTER_GLOBALS.outputs,
1138
+ port,
1139
+ data.element,
1140
+ data.bounds,
1141
+ context,
1142
+ );
1143
+ const sampleAddress = this.module.i32.add(
1144
+ channelPointer,
1145
+ this.module.i32.mul(
1146
+ this.compileValue(data.frame, context),
1147
+ this.module.i32.const(port.size),
1148
+ ),
1149
+ );
1150
+ return this.loadScalar(port.scalar, sampleAddress);
1151
+ }
1152
+
1153
+ compilePortChannel(port, element, bounds, context) {
1154
+ if (!port.isArray) {
1155
+ if (element !== null) {
1156
+ this.fail("scalar audio port unexpectedly has an element index");
1157
+ }
1158
+ return this.module.i32.const(port.channel);
1159
+ }
1160
+ if (element === null) {
1161
+ this.fail("array audio port is missing its element index");
1162
+ }
1163
+ const index = this.compileBoundedIndex(element, port.channels, bounds, context);
1164
+ return this.module.i32.add(this.module.i32.const(port.channel), index);
1165
+ }
1166
+
1167
+ audioChannelPointer(globalName, port, element, bounds, context) {
1168
+ const staticChannel = this.staticPortChannel(port, element, bounds);
1169
+ if (staticChannel === null) {
1170
+ return this.loadAudioChannelPointer(
1171
+ globalName,
1172
+ this.compilePortChannel(port, element, bounds, context),
1173
+ );
1174
+ }
1175
+ const key = `${globalName}:${staticChannel}`;
1176
+ let local = context.audioChannelPointerCache.get(key);
1177
+ if (local === undefined) {
1178
+ local = this.allocateGeneratedLocal(
1179
+ context,
1180
+ "i32",
1181
+ `audio.${globalName.slice(6)}.${staticChannel}`,
1182
+ );
1183
+ context.audioChannelPointerCache.set(key, local);
1184
+ context.entryInitializers.push(
1185
+ this.module.local.set(
1186
+ local,
1187
+ this.loadAudioChannelPointer(
1188
+ globalName,
1189
+ this.module.i32.const(staticChannel),
1190
+ ),
1191
+ ),
1192
+ );
1193
+ }
1194
+ return this.module.local.get(local, binaryen.i32);
1195
+ }
1196
+
1197
+ loadAudioChannelPointer(globalName, channel) {
1198
+ const tableAddress = this.module.i32.add(
1199
+ this.module.global.get(globalName, binaryen.i32),
1200
+ this.module.i32.mul(channel, this.module.i32.const(4)),
1201
+ );
1202
+ return this.module.i32.load(0, 4, tableAddress);
1203
+ }
1204
+
1205
+ staticPortChannel(port, element, bounds) {
1206
+ if (!port.isArray) return port.channel;
1207
+ if (
1208
+ element?.kind !== "constant"
1209
+ || element.data?.type !== "i32"
1210
+ || !Number.isInteger(element.data.value)
1211
+ ) {
1212
+ return null;
1213
+ }
1214
+ let index = element.data.value;
1215
+ if (bounds === "clamp") {
1216
+ index = Math.min(port.channels - 1, Math.max(0, index));
1217
+ } else if (index < 0 || index >= port.channels) {
1218
+ return null;
1219
+ }
1220
+ return port.channel + index;
1221
+ }
1222
+
1223
+ compileConstDataLoad(data, context) {
1224
+ const item = this.constLayout[data.data];
1225
+ if (!item) {
1226
+ this.fail(`const data id ${data.data} is out of range`);
1227
+ }
1228
+ const index = this.compileBoundedIndex(data.index, item.len, data.bounds, context);
1229
+ const address = this.module.i32.add(
1230
+ this.module.i32.const(item.address),
1231
+ this.module.i32.mul(index, this.module.i32.const(this.scalarSize(item.scalar))),
1232
+ );
1233
+ return this.loadScalar(item.scalar, address);
1234
+ }
1235
+
1236
+ compileBufferLoad(data, context) {
1237
+ const buffer = this.requireBufferRef(data.buffer);
1238
+ return this.loadScalar(
1239
+ buffer.element,
1240
+ this.compileBufferAddress(data, context, false),
1241
+ );
1242
+ }
1243
+
1244
+ compileBufferParamLoad(data, context) {
1245
+ const type = this.bufferParamType(data.parameter, context);
1246
+ return this.loadScalar(
1247
+ type.data.element,
1248
+ this.compileBufferParamAddress(data, context, false),
1249
+ );
1250
+ }
1251
+
1252
+ compileBufferParamAddress(data, context, write) {
1253
+ const type = this.bufferParamType(data.parameter, context);
1254
+ return this.withBufferParamSelector(
1255
+ data.parameter,
1256
+ context,
1257
+ binaryen.i32,
1258
+ (selector, staticSelector, prelude) => {
1259
+ let frames = this.bufferParamComponentFactory(
1260
+ data.parameter,
1261
+ 2,
1262
+ "i32",
1263
+ selector,
1264
+ staticSelector,
1265
+ context,
1266
+ );
1267
+ let channels = this.bufferParamChannelsFactory(
1268
+ data.parameter,
1269
+ selector,
1270
+ staticSelector,
1271
+ context,
1272
+ );
1273
+ if (data.parameter?.kind === "array_element" && staticSelector === null) {
1274
+ frames = this.snapshotOperationValue(
1275
+ frames,
1276
+ "i32",
1277
+ "buffer_param.frames",
1278
+ prelude,
1279
+ context,
1280
+ );
1281
+ if (this.bufferChannelMetadata(
1282
+ type.data.channels,
1283
+ type.data.element,
1284
+ ).kind === "dynamic") {
1285
+ channels = this.snapshotOperationValue(
1286
+ channels,
1287
+ "i32",
1288
+ "buffer_param.channels",
1289
+ prelude,
1290
+ context,
1291
+ );
1292
+ }
1293
+ }
1294
+ const frame = this.compileDynamicBoundedIndex(
1295
+ () => this.compileValue(data.index, context),
1296
+ frames,
1297
+ data.bounds,
1298
+ context,
1299
+ true,
1300
+ );
1301
+ const index = data.channel === null
1302
+ ? frame
1303
+ : this.module.i32.add(
1304
+ this.module.i32.mul(frame, channels()),
1305
+ this.compileDynamicBoundedIndex(
1306
+ () => this.compileValue(data.channel, context),
1307
+ channels,
1308
+ data.bounds,
1309
+ context,
1310
+ true,
1311
+ ),
1312
+ );
1313
+ const pointer = this.bufferParamComponentFactory(
1314
+ data.parameter,
1315
+ write ? 1 : 0,
1316
+ "i32",
1317
+ selector,
1318
+ staticSelector,
1319
+ context,
1320
+ );
1321
+ return this.bufferPointerWithOffset(
1322
+ pointer,
1323
+ this.module.i32.mul(
1324
+ index,
1325
+ this.module.i32.const(this.scalarSize(type.data.element)),
1326
+ ),
1327
+ write,
1328
+ context,
1329
+ );
1330
+ },
1331
+ );
1332
+ }
1333
+
1334
+ compileBufferAddress(data, context, write) {
1335
+ const buffer = this.requireBufferRef(data.buffer);
1336
+ return this.withBufferRefIndex(
1337
+ data.buffer,
1338
+ context,
1339
+ binaryen.i32,
1340
+ (descriptorIndex, staticIndex, prelude) => {
1341
+ let frames = this.bufferTableValueFactory(
1342
+ POINTER_GLOBALS.bufferFrames,
1343
+ descriptorIndex,
1344
+ staticIndex,
1345
+ "i32",
1346
+ context,
1347
+ );
1348
+ let channels = this.bufferChannelsFactory(
1349
+ data.buffer,
1350
+ descriptorIndex,
1351
+ staticIndex,
1352
+ context,
1353
+ );
1354
+ if (staticIndex === null) {
1355
+ frames = this.snapshotOperationValue(
1356
+ frames,
1357
+ "i32",
1358
+ "buffer.frames",
1359
+ prelude,
1360
+ context,
1361
+ );
1362
+ if (this.bufferRefChannelMetadata(data.buffer).kind === "dynamic") {
1363
+ channels = this.snapshotOperationValue(
1364
+ channels,
1365
+ "i32",
1366
+ "buffer.channels",
1367
+ prelude,
1368
+ context,
1369
+ );
1370
+ }
1371
+ }
1372
+ const frame = this.compileDynamicBoundedIndex(
1373
+ () => this.compileValue(data.index, context),
1374
+ frames,
1375
+ data.bounds,
1376
+ context,
1377
+ true,
1378
+ );
1379
+ const index = data.channel === null
1380
+ ? frame
1381
+ : this.module.i32.add(
1382
+ this.module.i32.mul(frame, channels()),
1383
+ this.compileDynamicBoundedIndex(
1384
+ () => this.compileValue(data.channel, context),
1385
+ channels,
1386
+ data.bounds,
1387
+ context,
1388
+ true,
1389
+ ),
1390
+ );
1391
+ const pointer = this.bufferTableValueFactory(
1392
+ write ? POINTER_GLOBALS.bufferWrites : POINTER_GLOBALS.buffers,
1393
+ descriptorIndex,
1394
+ staticIndex,
1395
+ "i32",
1396
+ context,
1397
+ );
1398
+ return this.bufferPointerWithOffset(
1399
+ pointer,
1400
+ this.module.i32.mul(
1401
+ index,
1402
+ this.module.i32.const(this.scalarSize(buffer.element)),
1403
+ ),
1404
+ write,
1405
+ context,
1406
+ );
1407
+ },
1408
+ );
1409
+ }
1410
+
1411
+ bufferPointerWithOffset(pointer, byteOffset, write, context) {
1412
+ const local = this.allocateGeneratedLocal(
1413
+ context,
1414
+ "i32",
1415
+ write ? "buffer.write_pointer" : "buffer.read_pointer",
1416
+ );
1417
+ const stablePointer = () => this.module.local.get(local, binaryen.i32);
1418
+ const fallback = write
1419
+ ? this.fallbackBufferWriteAddress
1420
+ : this.fallbackBufferReadAddress;
1421
+ return this.module.block(
1422
+ null,
1423
+ [
1424
+ this.module.local.set(local, pointer()),
1425
+ this.module.i32.add(
1426
+ stablePointer(),
1427
+ this.module.select(
1428
+ this.module.i32.eq(
1429
+ stablePointer(),
1430
+ this.module.i32.const(fallback),
1431
+ ),
1432
+ this.module.i32.const(0),
1433
+ byteOffset,
1434
+ ),
1435
+ ),
1436
+ ],
1437
+ binaryen.i32,
1438
+ );
1439
+ }
1440
+
1441
+ compileBufferLen(bufferRef, context) {
1442
+ this.requireBufferRef(bufferRef);
1443
+ return this.withBufferRefIndex(
1444
+ bufferRef,
1445
+ context,
1446
+ binaryen.i32,
1447
+ (index, staticIndex) => this.bufferTableValueFactory(
1448
+ POINTER_GLOBALS.bufferFrames,
1449
+ index,
1450
+ staticIndex,
1451
+ "i32",
1452
+ context,
1453
+ )(),
1454
+ );
1455
+ }
1456
+
1457
+ compileBufferIsBound(bufferRef, context) {
1458
+ this.requireBufferRef(bufferRef);
1459
+ return this.withBufferRefIndex(
1460
+ bufferRef,
1461
+ context,
1462
+ binaryen.i32,
1463
+ (index, staticIndex) => this.bufferBoundFactory(
1464
+ index,
1465
+ staticIndex,
1466
+ context,
1467
+ )(),
1468
+ );
1469
+ }
1470
+
1471
+ compileBufferChannels(bufferRef, context) {
1472
+ const channels = this.bufferRefChannelMetadata(bufferRef);
1473
+ if (channels.kind === "mono") return this.module.i32.const(1);
1474
+ if (channels.kind === "static") return this.module.i32.const(channels.count);
1475
+ return this.withBufferRefIndex(
1476
+ bufferRef,
1477
+ context,
1478
+ binaryen.i32,
1479
+ (index, staticIndex) => this.bufferTableValueFactory(
1480
+ POINTER_GLOBALS.bufferChannels,
1481
+ index,
1482
+ staticIndex,
1483
+ "i32",
1484
+ context,
1485
+ )(),
1486
+ );
1487
+ }
1488
+
1489
+ compileBufferParamLen(parameterId, context) {
1490
+ this.bufferParamType(parameterId, context);
1491
+ return this.withBufferParamSelector(
1492
+ parameterId,
1493
+ context,
1494
+ binaryen.i32,
1495
+ (selector, staticSelector) => this.bufferParamComponentFactory(
1496
+ parameterId,
1497
+ 2,
1498
+ "i32",
1499
+ selector,
1500
+ staticSelector,
1501
+ context,
1502
+ )(),
1503
+ );
1504
+ }
1505
+
1506
+ compileBufferParamChannels(parameterId, context) {
1507
+ const type = this.bufferParamType(parameterId, context);
1508
+ const channels = this.bufferChannelMetadata(
1509
+ type.data.channels,
1510
+ type.data.element,
1511
+ );
1512
+ if (channels.kind === "mono") return this.module.i32.const(1);
1513
+ if (channels.kind === "static") return this.module.i32.const(channels.count);
1514
+ return this.withBufferParamSelector(
1515
+ parameterId,
1516
+ context,
1517
+ binaryen.i32,
1518
+ (selector, staticSelector) => this.bufferParamComponentFactory(
1519
+ parameterId,
1520
+ 3,
1521
+ "i32",
1522
+ selector,
1523
+ staticSelector,
1524
+ context,
1525
+ )(),
1526
+ );
1527
+ }
1528
+
1529
+ bufferParamType(parameterId, context) {
1530
+ const parameter = context.function.params[this.bufferParamIds(parameterId, context)[0]];
1531
+ const type = parameter && this.type(parameter.ty);
1532
+ const expectedKind = parameterId?.kind === "array_element"
1533
+ ? "buffer_span"
1534
+ : "buffer";
1535
+ if (!type || type.kind !== expectedKind) {
1536
+ this.fail(`parameter id ${parameterId} is not a buffer`);
1537
+ }
1538
+ return type;
1539
+ }
1540
+
1541
+ bufferParamIds(parameterRef, context) {
1542
+ if (Number.isInteger(parameterRef)) return [parameterRef];
1543
+ if (
1544
+ parameterRef?.kind === "direct"
1545
+ && Number.isInteger(parameterRef.data)
1546
+ ) {
1547
+ return [parameterRef.data];
1548
+ }
1549
+ if (
1550
+ parameterRef?.kind === "array_element"
1551
+ && Number.isInteger(parameterRef.data?.span)
1552
+ && parameterRef.data.span >= 0
1553
+ && parameterRef.data.span < context.function.params.length
1554
+ ) {
1555
+ return [parameterRef.data.span];
1556
+ }
1557
+ this.fail("invalid buffer parameter reference");
1558
+ }
1559
+
1560
+ bufferParamLayout(parameterId, context) {
1561
+ const layout = context.paramLayouts[parameterId];
1562
+ if (!layout || !["buffer", "buffer_span"].includes(layout.kind)) {
1563
+ this.fail(`parameter id ${parameterId} has no buffer descriptor`);
1564
+ }
1565
+ return layout;
1566
+ }
1567
+
1568
+ staticBufferParamSelector(parameterRef, context) {
1569
+ if (parameterRef?.kind !== "array_element") return null;
1570
+ const reference = parameterRef.data;
1571
+ const type = this.bufferParamType(parameterRef, context);
1572
+ const selector = reference.selector;
1573
+ if (
1574
+ selector?.kind !== "constant"
1575
+ || selector.data?.type !== "i32"
1576
+ || !Number.isInteger(selector.data.value)
1577
+ ) {
1578
+ return null;
1579
+ }
1580
+ let index = selector.data.value;
1581
+ if (reference.bounds === "clamp") {
1582
+ index = Math.min(type.data.len - 1, Math.max(0, index));
1583
+ } else if (index < 0 || index >= type.data.len) {
1584
+ return null;
1585
+ }
1586
+ return index;
1587
+ }
1588
+
1589
+ compileBufferParamSelector(parameterRef, context) {
1590
+ const reference = parameterRef.data;
1591
+ const type = this.bufferParamType(parameterRef, context);
1592
+ return this.compileDynamicBoundedIndex(
1593
+ () => this.compileValue(reference.selector, context),
1594
+ () => this.module.i32.const(type.data.len),
1595
+ reference.bounds,
1596
+ context,
1597
+ true,
1598
+ );
1599
+ }
1600
+
1601
+ withBufferParamSelector(parameterRef, context, resultType, build) {
1602
+ if (parameterRef?.kind !== "array_element") {
1603
+ return build(null, null, []);
1604
+ }
1605
+ const staticSelector = this.staticBufferParamSelector(parameterRef, context);
1606
+ if (staticSelector !== null) {
1607
+ return build(
1608
+ () => this.module.i32.const(staticSelector),
1609
+ staticSelector,
1610
+ [],
1611
+ );
1612
+ }
1613
+ const selectorLocal = this.allocateGeneratedLocal(
1614
+ context,
1615
+ "i32",
1616
+ "buffer_param.selector",
1617
+ );
1618
+ const prelude = [
1619
+ this.module.local.set(
1620
+ selectorLocal,
1621
+ this.compileBufferParamSelector(parameterRef, context),
1622
+ ),
1623
+ ];
1624
+ const result = build(
1625
+ () => this.module.local.get(selectorLocal, binaryen.i32),
1626
+ null,
1627
+ prelude,
1628
+ );
1629
+ return this.module.block(null, [...prelude, result], resultType);
1630
+ }
1631
+
1632
+ bufferParamComponentFactory(
1633
+ parameterRef,
1634
+ offset,
1635
+ scalar,
1636
+ selector,
1637
+ staticSelector,
1638
+ context,
1639
+ ) {
1640
+ const parameterId = this.bufferParamIds(parameterRef, context)[0];
1641
+ const layout = this.bufferParamLayout(parameterId, context);
1642
+ if (parameterRef?.kind !== "array_element") {
1643
+ return () =>
1644
+ this.module.local.get(layout.index + offset, this.wasmType(scalar));
1645
+ }
1646
+ const rawLoad = (index) => {
1647
+ const table = this.module.local.get(layout.index + offset, binaryen.i32);
1648
+ const address = this.module.i32.add(
1649
+ table,
1650
+ this.module.i32.mul(
1651
+ index,
1652
+ this.module.i32.const(this.scalarSize(scalar)),
1653
+ ),
1654
+ );
1655
+ return this.loadScalar(scalar, address);
1656
+ };
1657
+ const load = (index) => {
1658
+ if (offset === 5) {
1659
+ return this.module.i32.ne(rawLoad(index), this.module.i32.const(0));
1660
+ }
1661
+ if (scalar !== "i32" || (offset !== 0 && offset !== 1)) {
1662
+ return rawLoad(index);
1663
+ }
1664
+ return this.resolveBufferPointer(
1665
+ () => rawLoad(index),
1666
+ offset === 1,
1667
+ context,
1668
+ );
1669
+ };
1670
+ if (staticSelector === null) return () => load(selector());
1671
+
1672
+ const key = `buffer_param:${parameterId}:${staticSelector}:${offset}:${scalar}`;
1673
+ let local = context.bufferDescriptorCache.get(key);
1674
+ if (local === undefined) {
1675
+ local = this.allocateGeneratedLocal(
1676
+ context,
1677
+ scalar,
1678
+ `buffer_param.component${offset}.${parameterId}.${staticSelector}`,
1679
+ );
1680
+ context.bufferDescriptorCache.set(key, local);
1681
+ context.entryInitializers.push(
1682
+ this.module.local.set(
1683
+ local,
1684
+ load(this.module.i32.const(staticSelector)),
1685
+ ),
1686
+ );
1687
+ }
1688
+ return () => this.module.local.get(local, this.wasmType(scalar));
1689
+ }
1690
+
1691
+ bufferParamChannelsFactory(
1692
+ parameterRef,
1693
+ selector,
1694
+ staticSelector,
1695
+ context,
1696
+ ) {
1697
+ const type = this.bufferParamType(parameterRef, context);
1698
+ const channels = this.bufferChannelMetadata(
1699
+ type.data.channels,
1700
+ type.data.element,
1701
+ );
1702
+ if (channels.kind === "mono") return () => this.module.i32.const(1);
1703
+ if (channels.kind === "static") {
1704
+ return () => this.module.i32.const(channels.count);
1705
+ }
1706
+ return this.bufferParamComponentFactory(
1707
+ parameterRef,
1708
+ 3,
1709
+ "i32",
1710
+ selector,
1711
+ staticSelector,
1712
+ context,
1713
+ );
1714
+ }
1715
+
1716
+ loadBufferParamComponent(parameterId, offset, scalar, context) {
1717
+ return this.withBufferParamSelector(
1718
+ parameterId,
1719
+ context,
1720
+ this.wasmType(scalar),
1721
+ (selector, staticSelector) => this.bufferParamComponentFactory(
1722
+ parameterId,
1723
+ offset,
1724
+ scalar,
1725
+ selector,
1726
+ staticSelector,
1727
+ context,
1728
+ )(),
1729
+ );
1730
+ }
1731
+
1732
+ loadBufferParamValue(parameterId, context) {
1733
+ const components = ["i32", "i32", "i32", "i32", "f32", "i32"];
1734
+ if (parameterId?.kind !== "array_element") {
1735
+ return components.map((scalar, offset) =>
1736
+ this.loadBufferParamComponent(parameterId, offset, scalar, context),
1737
+ );
1738
+ }
1739
+ const staticSelector = this.staticBufferParamSelector(parameterId, context);
1740
+ let selector;
1741
+ let initializeSelector = null;
1742
+ if (staticSelector === null) {
1743
+ const selectorLocal = this.allocateGeneratedLocal(
1744
+ context,
1745
+ "i32",
1746
+ "buffer_param.selector",
1747
+ );
1748
+ selector = () => this.module.local.get(selectorLocal, binaryen.i32);
1749
+ initializeSelector = this.module.local.set(
1750
+ selectorLocal,
1751
+ this.compileBufferParamSelector(parameterId, context),
1752
+ );
1753
+ } else {
1754
+ selector = () => this.module.i32.const(staticSelector);
1755
+ }
1756
+ const values = components.map((scalar, offset) =>
1757
+ this.bufferParamComponentFactory(
1758
+ parameterId,
1759
+ offset,
1760
+ scalar,
1761
+ selector,
1762
+ staticSelector,
1763
+ context,
1764
+ )(),
1765
+ );
1766
+ if (initializeSelector !== null) {
1767
+ values[0] = this.module.block(
1768
+ null,
1769
+ [initializeSelector, values[0]],
1770
+ binaryen.i32,
1771
+ );
1772
+ }
1773
+ return values;
1774
+ }
1775
+
1776
+ loadBufferPlace(place, context) {
1777
+ if (place.base.kind !== "parameter" || place.projections.length !== 0) {
1778
+ this.fail("buffer call arguments must be unprojected buffer parameters");
1779
+ }
1780
+ const layout = this.bufferParamLayout(place.base.data, context);
1781
+ return layout.components.map((scalar, offset) =>
1782
+ this.module.local.get(layout.index + offset, this.wasmType(scalar)),
1783
+ );
1784
+ }
1785
+
1786
+ compileInterfaceBufferValue(bufferRef, context) {
1787
+ this.requireBufferRef(bufferRef);
1788
+ const staticIndex = this.staticBufferRefIndex(bufferRef);
1789
+ let descriptorIndex;
1790
+ let initializeIndex = null;
1791
+ if (staticIndex === null) {
1792
+ const indexLocal = this.allocateGeneratedLocal(
1793
+ context,
1794
+ "i32",
1795
+ "buffer.descriptor_index",
1796
+ );
1797
+ descriptorIndex = () =>
1798
+ this.module.local.get(indexLocal, binaryen.i32);
1799
+ initializeIndex = this.module.local.set(
1800
+ indexLocal,
1801
+ this.compileBufferRefIndex(bufferRef, context),
1802
+ );
1803
+ } else {
1804
+ descriptorIndex = () => this.module.i32.const(staticIndex);
1805
+ }
1806
+ const component = (globalName, scalar) => this.bufferTableValueFactory(
1807
+ globalName,
1808
+ descriptorIndex,
1809
+ staticIndex,
1810
+ scalar,
1811
+ context,
1812
+ )();
1813
+ const channels = this.bufferRefChannelMetadata(bufferRef);
1814
+ const values = [
1815
+ component(POINTER_GLOBALS.buffers, "i32"),
1816
+ component(POINTER_GLOBALS.bufferWrites, "i32"),
1817
+ component(POINTER_GLOBALS.bufferFrames, "i32"),
1818
+ channels.kind === "mono"
1819
+ ? this.module.i32.const(1)
1820
+ : channels.kind === "static"
1821
+ ? this.module.i32.const(channels.count)
1822
+ : component(POINTER_GLOBALS.bufferChannels, "i32"),
1823
+ component(POINTER_GLOBALS.bufferSampleRates, "f32"),
1824
+ this.bufferBoundFactory(descriptorIndex, staticIndex, context)(),
1825
+ ];
1826
+ if (initializeIndex !== null) {
1827
+ values[0] = this.module.block(
1828
+ null,
1829
+ [initializeIndex, values[0]],
1830
+ binaryen.i32,
1831
+ );
1832
+ }
1833
+ return values;
1834
+ }
1835
+
1836
+ compileBufferSpanValue(spanRef, expectedType, context) {
1837
+ const data = spanRef?.data;
1838
+ if (!data || data.len !== expectedType.data.len) {
1839
+ this.fail("buffer span argument length does not match parameter type");
1840
+ }
1841
+ const tableGlobals = [
1842
+ POINTER_GLOBALS.buffers,
1843
+ POINTER_GLOBALS.bufferWrites,
1844
+ POINTER_GLOBALS.bufferFrames,
1845
+ POINTER_GLOBALS.bufferChannels,
1846
+ POINTER_GLOBALS.bufferSampleRates,
1847
+ POINTER_GLOBALS.buffers,
1848
+ ];
1849
+ const tableScalars = ["i32", "i32", "i32", "i32", "f32", "i32"];
1850
+ let tables;
1851
+ let start;
1852
+ if (spanRef.kind === "interface") {
1853
+ if (
1854
+ !Number.isInteger(data.first)
1855
+ || data.first < 0
1856
+ || data.first + data.len > this.mir.interface.buffers.length
1857
+ ) {
1858
+ this.fail("interface buffer span is out of range");
1859
+ }
1860
+ tables = tableGlobals.map((name) => this.module.global.get(name, binaryen.i32));
1861
+ start = data.first;
1862
+ } else if (spanRef.kind === "parameter") {
1863
+ const parameter = context.function.params[data.span];
1864
+ const sourceType = parameter && this.type(parameter.ty);
1865
+ const layout = context.paramLayouts[data.span];
1866
+ if (
1867
+ !sourceType
1868
+ || sourceType.kind !== "buffer_span"
1869
+ || !layout
1870
+ || layout.kind !== "buffer_span"
1871
+ || !Number.isInteger(data.start)
1872
+ || data.start < 0
1873
+ || data.start + data.len > sourceType.data.len
1874
+ ) {
1875
+ this.fail("buffer span parameter window is out of range");
1876
+ }
1877
+ tables = layout.components.map((_, offset) =>
1878
+ this.module.local.get(layout.index + offset, binaryen.i32));
1879
+ start = data.start;
1880
+ } else {
1881
+ this.fail("invalid buffer span reference");
1882
+ }
1883
+ return tables.map((table, offset) => this.module.i32.add(
1884
+ table,
1885
+ this.module.i32.const(start * this.scalarSize(tableScalars[offset])),
1886
+ ));
1887
+ }
1888
+
1889
+ loadBufferTableValue(globalName, bufferRef, scalar, context) {
1890
+ this.requireBufferRef(bufferRef);
1891
+ return this.withBufferRefIndex(
1892
+ bufferRef,
1893
+ context,
1894
+ this.wasmType(scalar),
1895
+ (index, staticIndex) => this.bufferTableValueFactory(
1896
+ globalName,
1897
+ index,
1898
+ staticIndex,
1899
+ scalar,
1900
+ context,
1901
+ )(),
1902
+ );
1903
+ }
1904
+
1905
+ bufferTableValueFactory(
1906
+ globalName,
1907
+ descriptorIndex,
1908
+ staticIndex,
1909
+ scalar,
1910
+ context,
1911
+ ) {
1912
+ const load = () => {
1913
+ const raw = () => this.loadBufferTableValueAt(
1914
+ globalName,
1915
+ descriptorIndex(),
1916
+ scalar,
1917
+ );
1918
+ if (
1919
+ scalar === "i32"
1920
+ && (globalName === POINTER_GLOBALS.buffers
1921
+ || globalName === POINTER_GLOBALS.bufferWrites)
1922
+ ) {
1923
+ return this.resolveBufferPointer(
1924
+ raw,
1925
+ globalName === POINTER_GLOBALS.bufferWrites,
1926
+ context,
1927
+ );
1928
+ }
1929
+ return raw();
1930
+ };
1931
+ if (staticIndex === null) {
1932
+ return load;
1933
+ }
1934
+ const key = `${globalName}:${staticIndex}:${scalar}`;
1935
+ let local = context.bufferDescriptorCache.get(key);
1936
+ if (local === undefined) {
1937
+ local = this.allocateGeneratedLocal(
1938
+ context,
1939
+ scalar,
1940
+ `buffer.${globalName.slice(6)}.${staticIndex}`,
1941
+ );
1942
+ context.bufferDescriptorCache.set(key, local);
1943
+ context.entryInitializers.push(
1944
+ this.module.local.set(
1945
+ local,
1946
+ load(),
1947
+ ),
1948
+ );
1949
+ }
1950
+ return () => this.module.local.get(local, this.wasmType(scalar));
1951
+ }
1952
+
1953
+ bufferBoundFactory(descriptorIndex, staticIndex, context) {
1954
+ const load = () => this.module.i32.ne(
1955
+ this.loadBufferTableValueAt(
1956
+ POINTER_GLOBALS.buffers,
1957
+ descriptorIndex(),
1958
+ "i32",
1959
+ ),
1960
+ this.module.i32.const(0),
1961
+ );
1962
+ if (staticIndex === null) return load;
1963
+
1964
+ const key = `buffer_bound:${staticIndex}`;
1965
+ let local = context.bufferDescriptorCache.get(key);
1966
+ if (local === undefined) {
1967
+ local = this.allocateGeneratedLocal(
1968
+ context,
1969
+ "i32",
1970
+ `buffer.bound.${staticIndex}`,
1971
+ );
1972
+ context.bufferDescriptorCache.set(key, local);
1973
+ context.entryInitializers.push(this.module.local.set(local, load()));
1974
+ }
1975
+ return () => this.module.local.get(local, binaryen.i32);
1976
+ }
1977
+
1978
+ bufferChannelsFactory(
1979
+ bufferRef,
1980
+ descriptorIndex,
1981
+ staticIndex,
1982
+ context,
1983
+ ) {
1984
+ const channels = this.bufferRefChannelMetadata(bufferRef);
1985
+ if (channels.kind === "mono") {
1986
+ return () => this.module.i32.const(1);
1987
+ }
1988
+ if (channels.kind === "static") {
1989
+ return () => this.module.i32.const(channels.count);
1990
+ }
1991
+ return this.bufferTableValueFactory(
1992
+ POINTER_GLOBALS.bufferChannels,
1993
+ descriptorIndex,
1994
+ staticIndex,
1995
+ "i32",
1996
+ context,
1997
+ );
1998
+ }
1999
+
2000
+ loadBufferTableValueAt(globalName, descriptorIndex, scalar) {
2001
+ const size = this.scalarSize(scalar);
2002
+ const load = () => this.loadScalar(
2003
+ scalar,
2004
+ this.module.i32.add(
2005
+ this.module.global.get(globalName, binaryen.i32),
2006
+ this.module.i32.mul(
2007
+ descriptorIndex,
2008
+ this.module.i32.const(size),
2009
+ ),
2010
+ ),
2011
+ );
2012
+ return load();
2013
+ }
2014
+
2015
+ resolveBufferPointer(load, write, context) {
2016
+ const local = this.allocateGeneratedLocal(
2017
+ context,
2018
+ "i32",
2019
+ write ? "buffer.write_or_discard" : "buffer.read_or_zero",
2020
+ );
2021
+ const pointer = () => this.module.local.get(local, binaryen.i32);
2022
+ return this.module.block(
2023
+ null,
2024
+ [
2025
+ this.module.local.set(local, load()),
2026
+ this.module.select(
2027
+ this.module.i32.ne(pointer(), this.module.i32.const(0)),
2028
+ pointer(),
2029
+ this.module.i32.const(
2030
+ write
2031
+ ? this.fallbackBufferWriteAddress
2032
+ : this.fallbackBufferReadAddress,
2033
+ ),
2034
+ ),
2035
+ ],
2036
+ binaryen.i32,
2037
+ );
2038
+ }
2039
+
2040
+ withBufferRefIndex(bufferRef, context, resultType, build) {
2041
+ const staticIndex = this.staticBufferRefIndex(bufferRef);
2042
+ if (staticIndex !== null) {
2043
+ return build(
2044
+ () => this.module.i32.const(staticIndex),
2045
+ staticIndex,
2046
+ [],
2047
+ );
2048
+ }
2049
+ const indexLocal = this.allocateGeneratedLocal(
2050
+ context,
2051
+ "i32",
2052
+ "buffer.descriptor_index",
2053
+ );
2054
+ const prelude = [
2055
+ this.module.local.set(
2056
+ indexLocal,
2057
+ this.compileBufferRefIndex(bufferRef, context),
2058
+ ),
2059
+ ];
2060
+ const result = build(
2061
+ () => this.module.local.get(indexLocal, binaryen.i32),
2062
+ null,
2063
+ prelude,
2064
+ );
2065
+ return this.module.block(null, [...prelude, result], resultType);
2066
+ }
2067
+
2068
+ snapshotOperationValue(factory, scalar, name, prelude, context) {
2069
+ const local = this.allocateGeneratedLocal(context, scalar, name);
2070
+ prelude.push(
2071
+ this.module.local.set(local, factory()),
2072
+ );
2073
+ return () => this.module.local.get(local, this.wasmType(scalar));
2074
+ }
2075
+
2076
+ allocateGeneratedLocal(context, scalar, name) {
2077
+ const index = context.generatedLocalBase + context.generatedLocals.length;
2078
+ context.generatedLocals.push({
2079
+ index,
2080
+ scalar,
2081
+ name: `${name}.generated${context.generatedLocals.length}`,
2082
+ });
2083
+ return index;
2084
+ }
2085
+
2086
+ compileSliceValue(value, context) {
2087
+ if (value.kind !== "local") {
2088
+ this.fail("slice values must reside in MIR locals");
2089
+ }
2090
+ const layout = context.localLayouts[value.data];
2091
+ if (!layout || layout.kind !== "slice") {
2092
+ this.fail(`local id ${value.data} is not a slice`);
2093
+ }
2094
+ return layout.components.map((scalar, offset) =>
2095
+ this.module.local.get(layout.index + offset, this.wasmType(scalar)),
2096
+ );
2097
+ }
2098
+
2099
+ loadSlicePlace(place, context) {
2100
+ if (place.projections.length !== 0) {
2101
+ this.fail("slice places cannot have projections");
2102
+ }
2103
+ let layout;
2104
+ if (place.base.kind === "local") {
2105
+ layout = context.localLayouts[place.base.data];
2106
+ } else if (place.base.kind === "parameter") {
2107
+ layout = context.paramLayouts[place.base.data];
2108
+ } else if (place.base.kind === "event_param") {
2109
+ const event = this.mir.interface.events[context.eventId];
2110
+ const parameter = event?.params[place.base.data];
2111
+ const type = parameter && this.type(parameter.ty);
2112
+ if (!type || type.kind !== "slice") {
2113
+ this.fail(`event parameter id ${place.base.data} is not a slice`);
2114
+ }
2115
+ const header = () =>
2116
+ this.compileEventParamAddress(context.eventId, place.base.data);
2117
+ const address = () =>
2118
+ this.module.i32.add(header(), this.module.i32.const(4));
2119
+ return [
2120
+ address(),
2121
+ address(),
2122
+ this.module.i32.load(0, 4, header()),
2123
+ this.module.i32.const(this.scalarSize(type.data.element)),
2124
+ ];
2125
+ } else {
2126
+ this.fail(`slice place base '${place.base.kind}' is not supported yet`);
2127
+ }
2128
+ if (!layout || layout.kind !== "slice") {
2129
+ this.fail(`place base '${place.base.kind}' id ${place.base.data} is not a slice`);
2130
+ }
2131
+ return layout.components.map((scalar, offset) =>
2132
+ this.module.local.get(layout.index + offset, this.wasmType(scalar)),
2133
+ );
2134
+ }
2135
+
2136
+ compileEventParamAddress(eventId, paramId) {
2137
+ const event = this.mir.interface.events[eventId];
2138
+ if (!event || !Number.isInteger(paramId) || paramId < 0 || paramId >= event.params.length) {
2139
+ this.fail(`event parameter id ${paramId} is invalid for event ${eventId}`);
2140
+ }
2141
+ let offset = () => this.module.i32.const(0);
2142
+ for (let index = 0; index < paramId; index += 1) {
2143
+ const previous = offset;
2144
+ const type = this.type(event.params[index].ty);
2145
+ if (type.kind === "slice") {
2146
+ const elementSize = this.scalarSize(type.data.element);
2147
+ offset = () =>
2148
+ this.module.i32.add(
2149
+ previous(),
2150
+ this.module.i32.add(
2151
+ this.module.i32.const(4),
2152
+ this.module.i32.mul(
2153
+ this.module.i32.load(
2154
+ 0,
2155
+ 4,
2156
+ this.module.i32.add(
2157
+ this.module.global.get(POINTER_GLOBALS.eventPayload, binaryen.i32),
2158
+ previous(),
2159
+ ),
2160
+ ),
2161
+ this.module.i32.const(elementSize),
2162
+ ),
2163
+ ),
2164
+ );
2165
+ } else {
2166
+ const size = this.typeLayout(event.params[index].ty).size;
2167
+ offset = () => this.module.i32.add(previous(), this.module.i32.const(size));
2168
+ }
2169
+ }
2170
+ return this.module.i32.add(
2171
+ this.module.global.get(POINTER_GLOBALS.eventPayload, binaryen.i32),
2172
+ offset(),
2173
+ );
2174
+ }
2175
+
2176
+ storeSlicePlace(place, components, context) {
2177
+ if (place.base.kind !== "local" || place.projections.length !== 0) {
2178
+ this.fail("slice assignment destination must be an unprojected local");
2179
+ }
2180
+ const layout = context.localLayouts[place.base.data];
2181
+ if (!layout || layout.kind !== "slice" || components.length !== 4) {
2182
+ this.fail(`local id ${place.base.data} is not a valid slice destination`);
2183
+ }
2184
+ return this.module.block(
2185
+ null,
2186
+ components.map((component, offset) =>
2187
+ this.module.local.set(layout.index + offset, component),
2188
+ ),
2189
+ );
2190
+ }
2191
+
2192
+ compileMakeSlice(data, context) {
2193
+ const sourceValues = this.compileSliceSource(data.source, context);
2194
+ const sourceLocals = sourceValues.map((_, offset) =>
2195
+ this.allocateGeneratedLocal(
2196
+ context,
2197
+ "i32",
2198
+ `slice.source_component${offset}`,
2199
+ ),
2200
+ );
2201
+ const initializeSource = sourceValues.map((value, offset) =>
2202
+ this.module.local.set(sourceLocals[offset], value),
2203
+ );
2204
+ const source = (offset) => () =>
2205
+ this.module.local.get(sourceLocals[offset], binaryen.i32);
2206
+ const range = this.compileSliceRange(
2207
+ () => this.compileValue(data.start, context),
2208
+ () => this.compileValue(data.len, context),
2209
+ source(2),
2210
+ data.bounds,
2211
+ context,
2212
+ );
2213
+ const result = [
2214
+ this.module.i32.add(
2215
+ source(0)(),
2216
+ this.module.i32.mul(
2217
+ range.start(),
2218
+ source(3)(),
2219
+ ),
2220
+ ),
2221
+ this.module.i32.add(
2222
+ source(1)(),
2223
+ this.module.i32.mul(
2224
+ range.start(),
2225
+ source(3)(),
2226
+ ),
2227
+ ),
2228
+ range.len(),
2229
+ source(3)(),
2230
+ ];
2231
+ result[0] = this.module.block(
2232
+ null,
2233
+ [...initializeSource, result[0]],
2234
+ binaryen.i32,
2235
+ );
2236
+ return result;
2237
+ }
2238
+
2239
+ compileSliceRange(start, len, sourceLen, bounds, context) {
2240
+ const zero = () => this.module.i32.const(0);
2241
+ if (bounds === "unchecked") {
2242
+ return { start, len };
2243
+ }
2244
+ if (bounds === "clamp") {
2245
+ const normalizedStart = () => {
2246
+ const low = () =>
2247
+ this.module.select(
2248
+ this.module.i32.lt_s(start(), zero()),
2249
+ zero(),
2250
+ start(),
2251
+ );
2252
+ return this.module.select(
2253
+ this.module.i32.gt_s(low(), sourceLen()),
2254
+ sourceLen(),
2255
+ low(),
2256
+ );
2257
+ };
2258
+ const normalizedLen = () => {
2259
+ const low = () =>
2260
+ this.module.select(
2261
+ this.module.i32.lt_s(len(), zero()),
2262
+ zero(),
2263
+ len(),
2264
+ );
2265
+ const remaining = () =>
2266
+ this.module.i32.sub(sourceLen(), normalizedStart());
2267
+ return this.module.select(
2268
+ this.module.i32.gt_s(low(), remaining()),
2269
+ remaining(),
2270
+ low(),
2271
+ );
2272
+ };
2273
+ return { start: normalizedStart, len: normalizedLen };
2274
+ }
2275
+ if (bounds === "checked") {
2276
+ const invalid = () => {
2277
+ const remaining = () => this.module.i32.sub(sourceLen(), start());
2278
+ return this.module.i32.or(
2279
+ this.module.i32.or(
2280
+ this.module.i32.lt_s(start(), zero()),
2281
+ this.module.i32.gt_s(start(), sourceLen()),
2282
+ ),
2283
+ this.module.i32.or(
2284
+ this.module.i32.lt_s(len(), zero()),
2285
+ this.module.i32.gt_s(len(), remaining()),
2286
+ ),
2287
+ );
2288
+ };
2289
+ return {
2290
+ start: () =>
2291
+ this.module.if(invalid(), this.raiseRuntimeFailure(context), start()),
2292
+ len,
2293
+ };
2294
+ }
2295
+ this.fail(`unknown bounds mode '${String(bounds)}'`);
2296
+ }
2297
+ }