@onda-lang/wasm-compiler 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -3
- package/bin/onda-wasm.js +1 -1
- package/dist/backend/constants.js +1 -1
- package/dist/backend/index.js +2427 -342
- package/dist/build.json +3 -3
- package/dist/frontend/README.md +12 -3
- package/dist/frontend/onda_compiler_web.d.ts +94 -5
- package/dist/frontend/onda_compiler_web.js +442 -4
- package/dist/frontend/onda_compiler_web_bg.wasm +0 -0
- package/dist/frontend/onda_compiler_web_bg.wasm.d.ts +34 -3
- package/dist/frontend/package.json +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/index.d.ts +106 -3
- package/src/index.js +400 -19
- package/src/worker.js +58 -3
package/dist/backend/index.js
CHANGED
|
@@ -64,10 +64,38 @@ const POINTER_GLOBALS = Object.freeze({
|
|
|
64
64
|
state: "$onda.state",
|
|
65
65
|
eventPayload: "$onda.event_payload",
|
|
66
66
|
buffers: "$onda.buffers",
|
|
67
|
+
bufferWrites: "$onda.buffer_writes",
|
|
67
68
|
bufferFrames: "$onda.buffer_frames",
|
|
68
69
|
bufferChannels: "$onda.buffer_channels",
|
|
69
70
|
bufferSampleRates: "$onda.buffer_sample_rates",
|
|
70
71
|
});
|
|
72
|
+
const BUFFER_DESCRIPTOR_POINTER_GLOBALS = new Set([
|
|
73
|
+
POINTER_GLOBALS.buffers,
|
|
74
|
+
POINTER_GLOBALS.bufferWrites,
|
|
75
|
+
POINTER_GLOBALS.bufferFrames,
|
|
76
|
+
POINTER_GLOBALS.bufferChannels,
|
|
77
|
+
POINTER_GLOBALS.bufferSampleRates,
|
|
78
|
+
]);
|
|
79
|
+
const TRAPPING_DESCRIPTOR_UNARY_OPS = new Set([
|
|
80
|
+
binaryen.TruncSFloat32ToInt32,
|
|
81
|
+
binaryen.TruncSFloat32ToInt64,
|
|
82
|
+
binaryen.TruncSFloat64ToInt32,
|
|
83
|
+
binaryen.TruncSFloat64ToInt64,
|
|
84
|
+
binaryen.TruncUFloat32ToInt32,
|
|
85
|
+
binaryen.TruncUFloat32ToInt64,
|
|
86
|
+
binaryen.TruncUFloat64ToInt32,
|
|
87
|
+
binaryen.TruncUFloat64ToInt64,
|
|
88
|
+
]);
|
|
89
|
+
const TRAPPING_DESCRIPTOR_BINARY_OPS = new Set([
|
|
90
|
+
binaryen.DivSInt32,
|
|
91
|
+
binaryen.DivSInt64,
|
|
92
|
+
binaryen.DivUInt32,
|
|
93
|
+
binaryen.DivUInt64,
|
|
94
|
+
binaryen.RemSInt32,
|
|
95
|
+
binaryen.RemSInt64,
|
|
96
|
+
binaryen.RemUInt32,
|
|
97
|
+
binaryen.RemUInt64,
|
|
98
|
+
]);
|
|
71
99
|
|
|
72
100
|
// Compiles MIR emitted by Onda's semantic producer. The producer owns proofs
|
|
73
101
|
// for operations marked `bounds: "unchecked"` and all other validated MIR
|
|
@@ -185,6 +213,10 @@ class MirCompiler {
|
|
|
185
213
|
: STATIC_BASE;
|
|
186
214
|
this.internalHelpers = new Set();
|
|
187
215
|
this.functionMayFail = [];
|
|
216
|
+
this.bufferMayWrite = [];
|
|
217
|
+
this.fallbackBufferReadAddress = 0;
|
|
218
|
+
this.fallbackBufferWriteAddress = 0;
|
|
219
|
+
this.scalarParameterByValue = [];
|
|
188
220
|
this.nextLabel = 0;
|
|
189
221
|
}
|
|
190
222
|
|
|
@@ -214,6 +246,21 @@ class MirCompiler {
|
|
|
214
246
|
this.options.allowInliningFunctionsWithLoops,
|
|
215
247
|
);
|
|
216
248
|
this.module.optimize();
|
|
249
|
+
if (this.hoistInvariantBufferDescriptorLoads()) {
|
|
250
|
+
// The first rewrite makes descriptor provenance explicit in
|
|
251
|
+
// locals. A small cleanup is enough to expose aliases that were
|
|
252
|
+
// shared by Binaryen's original loop body; one final rewrite then
|
|
253
|
+
// catches those without paying for a second full O4 pipeline.
|
|
254
|
+
this.module.runPasses([
|
|
255
|
+
"simplify-locals",
|
|
256
|
+
"optimize-instructions",
|
|
257
|
+
"coalesce-locals",
|
|
258
|
+
"vacuum",
|
|
259
|
+
]);
|
|
260
|
+
if (this.hoistInvariantBufferDescriptorLoads()) {
|
|
261
|
+
this.module.runPasses(["vacuum"]);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
217
264
|
} finally {
|
|
218
265
|
binaryen.setOptimizeLevel(previousOptimizeLevel);
|
|
219
266
|
binaryen.setShrinkLevel(previousShrinkLevel);
|
|
@@ -244,6 +291,509 @@ class MirCompiler {
|
|
|
244
291
|
}
|
|
245
292
|
}
|
|
246
293
|
|
|
294
|
+
hoistInvariantBufferDescriptorLoads() {
|
|
295
|
+
// Binaryen must conservatively assume that arbitrary linear-memory stores
|
|
296
|
+
// can rewrite host descriptor tables. After inlining, recover the stronger
|
|
297
|
+
// processor ABI contract explicitly: descriptor bindings are immutable for
|
|
298
|
+
// one entry-point invocation, so address-invariant loads belong in the loop
|
|
299
|
+
// preheader. Sample-varying addresses remain untouched.
|
|
300
|
+
this.descriptorLoadsHoisted = 0;
|
|
301
|
+
for (let index = 0; index < this.module.getNumFunctions(); index += 1) {
|
|
302
|
+
const func = this.module.getFunctionByIndex(index);
|
|
303
|
+
const body = binaryen.Function.getBody(func);
|
|
304
|
+
// The local-write scan below is part of the safety proof. If Binaryen
|
|
305
|
+
// adds an expression kind that this backend does not know how to walk,
|
|
306
|
+
// leave the whole function untouched rather than silently overlooking
|
|
307
|
+
// a nested local.tee.
|
|
308
|
+
if (!this.visitExpression(body, () => {})) continue;
|
|
309
|
+
const rewritten = this.rewriteDescriptorLoops(body, func);
|
|
310
|
+
if (rewritten !== body) binaryen.Function.setBody(func, rewritten);
|
|
311
|
+
}
|
|
312
|
+
return this.descriptorLoadsHoisted > 0;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
rewriteDescriptorLoops(expression, func) {
|
|
316
|
+
this.rewriteExpressionChildren(expression, (child) =>
|
|
317
|
+
this.rewriteDescriptorLoops(child, func)
|
|
318
|
+
);
|
|
319
|
+
if (binaryen.getExpressionInfo(expression).id !== binaryen.LoopId) {
|
|
320
|
+
return expression;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const body = binaryen.Loop.getBody(expression);
|
|
324
|
+
const controlPaths = this.descriptorControlPaths(body);
|
|
325
|
+
const definitions = new Map();
|
|
326
|
+
const writtenLocals = new Set();
|
|
327
|
+
this.visitExpression(body, (candidate) => {
|
|
328
|
+
const info = binaryen.getExpressionInfo(candidate);
|
|
329
|
+
if (info.id !== binaryen.LocalSetId) return;
|
|
330
|
+
writtenLocals.add(info.index);
|
|
331
|
+
const entries = definitions.get(info.index) ?? [];
|
|
332
|
+
entries.push(info.value);
|
|
333
|
+
definitions.set(info.index, entries);
|
|
334
|
+
});
|
|
335
|
+
const initializers = [];
|
|
336
|
+
// A pointer-local tee can expose the same invariant address to later
|
|
337
|
+
// descriptor loads. Cache its value in a fresh local: assigning the
|
|
338
|
+
// original local in the preheader would change loop-entry semantics.
|
|
339
|
+
const loopLocalCaches = new Map();
|
|
340
|
+
|
|
341
|
+
const rewriteLoad = (candidate) => {
|
|
342
|
+
this.rewriteExpressionChildren(candidate, rewriteLoad);
|
|
343
|
+
const info = binaryen.getExpressionInfo(candidate);
|
|
344
|
+
if (info.id !== binaryen.LoadId || info.isAtomic) return candidate;
|
|
345
|
+
const candidatePath = controlPaths.get(candidate) ?? [];
|
|
346
|
+
const localCache = (local) => {
|
|
347
|
+
const cache = loopLocalCaches.get(local);
|
|
348
|
+
return cache && this.descriptorPathDominates(cache.path, candidatePath)
|
|
349
|
+
? cache
|
|
350
|
+
: null;
|
|
351
|
+
};
|
|
352
|
+
if (!this.descriptorPointerExpression(
|
|
353
|
+
info.ptr,
|
|
354
|
+
definitions,
|
|
355
|
+
(local) => !writtenLocals.has(local) || localCache(local) !== null,
|
|
356
|
+
)) {
|
|
357
|
+
return candidate;
|
|
358
|
+
}
|
|
359
|
+
this.cacheDescriptorPointerSideEffects(
|
|
360
|
+
info.ptr,
|
|
361
|
+
func,
|
|
362
|
+
initializers,
|
|
363
|
+
loopLocalCaches,
|
|
364
|
+
controlPaths,
|
|
365
|
+
candidatePath,
|
|
366
|
+
);
|
|
367
|
+
// Binaryen exposes FunctionAddVar through its generated C-API surface,
|
|
368
|
+
// but not through the small Function convenience wrapper.
|
|
369
|
+
const cache = binaryen._BinaryenFunctionAddVar(func, info.type);
|
|
370
|
+
this.descriptorLoadsHoisted += 1;
|
|
371
|
+
const hoistedLoad = this.module.copyExpression(candidate);
|
|
372
|
+
const hoistedInfo = binaryen.getExpressionInfo(hoistedLoad);
|
|
373
|
+
binaryen.Load.setPtr(
|
|
374
|
+
hoistedLoad,
|
|
375
|
+
this.descriptorPointerForPreheader(
|
|
376
|
+
hoistedInfo.ptr,
|
|
377
|
+
loopLocalCaches,
|
|
378
|
+
candidatePath,
|
|
379
|
+
),
|
|
380
|
+
);
|
|
381
|
+
initializers.push(this.module.local.set(cache, hoistedLoad));
|
|
382
|
+
const sideEffects = this.descriptorPointerSideEffects(info.ptr);
|
|
383
|
+
const value = this.module.local.get(cache, info.type);
|
|
384
|
+
return sideEffects.length === 0
|
|
385
|
+
? value
|
|
386
|
+
: this.module.block(null, [...sideEffects, value], info.type);
|
|
387
|
+
};
|
|
388
|
+
const rewrittenBody = rewriteLoad(body);
|
|
389
|
+
if (rewrittenBody !== body) binaryen.Loop.setBody(expression, rewrittenBody);
|
|
390
|
+
return initializers.length === 0
|
|
391
|
+
? expression
|
|
392
|
+
: this.module.block(null, [...initializers, expression]);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
descriptorControlPaths(expression) {
|
|
396
|
+
const paths = new Map();
|
|
397
|
+
const visit = (candidate, path) => {
|
|
398
|
+
paths.set(candidate, path);
|
|
399
|
+
const info = binaryen.getExpressionInfo(candidate);
|
|
400
|
+
if (info.id === binaryen.IfId) {
|
|
401
|
+
visit(info.condition, path);
|
|
402
|
+
visit(info.ifTrue, [...path, `if:${candidate}:true`]);
|
|
403
|
+
if (info.ifFalse) {
|
|
404
|
+
visit(info.ifFalse, [...path, `if:${candidate}:false`]);
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (info.id === binaryen.LoopId) {
|
|
409
|
+
visit(info.body, [...path, `loop:${candidate}`]);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
this.rewriteExpressionChildren(candidate, (child) => {
|
|
413
|
+
visit(child, path);
|
|
414
|
+
return child;
|
|
415
|
+
});
|
|
416
|
+
};
|
|
417
|
+
visit(expression, []);
|
|
418
|
+
return paths;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
descriptorPathDominates(dominator, candidate) {
|
|
422
|
+
// Rewrite traversal is in evaluation order, so an available cache is
|
|
423
|
+
// earlier than the candidate. The path prefix additionally proves that
|
|
424
|
+
// it was not produced only in a sibling branch or nested loop.
|
|
425
|
+
return dominator.length <= candidate.length
|
|
426
|
+
&& dominator.every((entry, index) => entry === candidate[index]);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
cacheDescriptorPointerSideEffects(
|
|
430
|
+
expression,
|
|
431
|
+
func,
|
|
432
|
+
initializers,
|
|
433
|
+
loopLocalCaches,
|
|
434
|
+
controlPaths,
|
|
435
|
+
candidatePath,
|
|
436
|
+
) {
|
|
437
|
+
const info = binaryen.getExpressionInfo(expression);
|
|
438
|
+
if (info.id === binaryen.LocalSetId && info.isTee) {
|
|
439
|
+
this.cacheDescriptorPointerSideEffects(
|
|
440
|
+
info.value,
|
|
441
|
+
func,
|
|
442
|
+
initializers,
|
|
443
|
+
loopLocalCaches,
|
|
444
|
+
controlPaths,
|
|
445
|
+
candidatePath,
|
|
446
|
+
);
|
|
447
|
+
const cache = binaryen._BinaryenFunctionAddVar(func, info.type);
|
|
448
|
+
const value = this.descriptorPointerForPreheader(
|
|
449
|
+
this.module.copyExpression(info.value),
|
|
450
|
+
loopLocalCaches,
|
|
451
|
+
candidatePath,
|
|
452
|
+
);
|
|
453
|
+
initializers.push(this.module.local.set(cache, value));
|
|
454
|
+
loopLocalCaches.set(info.index, {
|
|
455
|
+
index: cache,
|
|
456
|
+
type: info.type,
|
|
457
|
+
path: controlPaths.get(expression) ?? candidatePath,
|
|
458
|
+
});
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (info.id === binaryen.UnaryId) {
|
|
462
|
+
this.cacheDescriptorPointerSideEffects(
|
|
463
|
+
info.value,
|
|
464
|
+
func,
|
|
465
|
+
initializers,
|
|
466
|
+
loopLocalCaches,
|
|
467
|
+
controlPaths,
|
|
468
|
+
candidatePath,
|
|
469
|
+
);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (info.id === binaryen.BinaryId) {
|
|
473
|
+
for (const child of [info.left, info.right]) {
|
|
474
|
+
this.cacheDescriptorPointerSideEffects(
|
|
475
|
+
child,
|
|
476
|
+
func,
|
|
477
|
+
initializers,
|
|
478
|
+
loopLocalCaches,
|
|
479
|
+
controlPaths,
|
|
480
|
+
candidatePath,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
descriptorPointerForPreheader(expression, loopLocalCaches, candidatePath) {
|
|
487
|
+
this.rewriteExpressionChildren(expression, (child) =>
|
|
488
|
+
this.descriptorPointerForPreheader(
|
|
489
|
+
child,
|
|
490
|
+
loopLocalCaches,
|
|
491
|
+
candidatePath,
|
|
492
|
+
)
|
|
493
|
+
);
|
|
494
|
+
const info = binaryen.getExpressionInfo(expression);
|
|
495
|
+
if (info.id === binaryen.LocalGetId) {
|
|
496
|
+
const cache = loopLocalCaches.get(info.index);
|
|
497
|
+
if (cache && this.descriptorPathDominates(cache.path, candidatePath)) {
|
|
498
|
+
return this.module.local.get(cache.index, cache.type);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (info.id === binaryen.LocalSetId && info.isTee) {
|
|
502
|
+
const cache = loopLocalCaches.get(info.index);
|
|
503
|
+
if (cache && this.descriptorPathDominates(cache.path, candidatePath)) {
|
|
504
|
+
return this.module.local.get(cache.index, cache.type);
|
|
505
|
+
}
|
|
506
|
+
return info.value;
|
|
507
|
+
}
|
|
508
|
+
return expression;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
descriptorPointerExpression(expression, definitions, localIsInvariant) {
|
|
512
|
+
if (!this.expressionUsesDescriptorTable(expression, definitions, new Set())) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
const visit = (candidate) => {
|
|
516
|
+
const info = binaryen.getExpressionInfo(candidate);
|
|
517
|
+
if (info.id === binaryen.ConstId) return true;
|
|
518
|
+
if (info.id === binaryen.LocalGetId) return localIsInvariant(info.index);
|
|
519
|
+
if (info.id === binaryen.GlobalGetId) {
|
|
520
|
+
if (BUFFER_DESCRIPTOR_POINTER_GLOBALS.has(info.name)) {
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
if (info.id === binaryen.LocalSetId && info.isTee) {
|
|
526
|
+
return visit(info.value);
|
|
527
|
+
}
|
|
528
|
+
if (info.id === binaryen.UnaryId) {
|
|
529
|
+
return !TRAPPING_DESCRIPTOR_UNARY_OPS.has(info.op)
|
|
530
|
+
&& visit(info.value);
|
|
531
|
+
}
|
|
532
|
+
if (info.id === binaryen.BinaryId) {
|
|
533
|
+
return !TRAPPING_DESCRIPTOR_BINARY_OPS.has(info.op)
|
|
534
|
+
&& visit(info.left)
|
|
535
|
+
&& visit(info.right);
|
|
536
|
+
}
|
|
537
|
+
if (info.id === binaryen.SelectId) {
|
|
538
|
+
if (
|
|
539
|
+
this.expressionContainsTee(info.condition)
|
|
540
|
+
|| this.expressionContainsTee(info.ifTrue)
|
|
541
|
+
|| this.expressionContainsTee(info.ifFalse)
|
|
542
|
+
) {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
return visit(info.condition) && visit(info.ifTrue) && visit(info.ifFalse);
|
|
546
|
+
}
|
|
547
|
+
return false;
|
|
548
|
+
};
|
|
549
|
+
return visit(expression);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
expressionUsesDescriptorTable(expression, definitions, visitingLocals) {
|
|
553
|
+
const info = binaryen.getExpressionInfo(expression);
|
|
554
|
+
if (info.id === binaryen.GlobalGetId) {
|
|
555
|
+
return BUFFER_DESCRIPTOR_POINTER_GLOBALS.has(info.name);
|
|
556
|
+
}
|
|
557
|
+
if (info.id === binaryen.LocalGetId) {
|
|
558
|
+
const values = definitions.get(info.index);
|
|
559
|
+
if (
|
|
560
|
+
!values
|
|
561
|
+
|| values.length !== 1
|
|
562
|
+
|| visitingLocals.has(info.index)
|
|
563
|
+
) {
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
visitingLocals.add(info.index);
|
|
567
|
+
const result = this.expressionUsesDescriptorTable(
|
|
568
|
+
values[0],
|
|
569
|
+
definitions,
|
|
570
|
+
visitingLocals,
|
|
571
|
+
);
|
|
572
|
+
visitingLocals.delete(info.index);
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
if (info.id === binaryen.LocalSetId && info.isTee) {
|
|
576
|
+
return this.expressionUsesDescriptorTable(
|
|
577
|
+
info.value,
|
|
578
|
+
definitions,
|
|
579
|
+
visitingLocals,
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
if (info.id === binaryen.UnaryId) {
|
|
583
|
+
return this.expressionUsesDescriptorTable(
|
|
584
|
+
info.value,
|
|
585
|
+
definitions,
|
|
586
|
+
visitingLocals,
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
if (info.id === binaryen.BinaryId) {
|
|
590
|
+
return this.expressionUsesDescriptorTable(
|
|
591
|
+
info.left,
|
|
592
|
+
definitions,
|
|
593
|
+
visitingLocals,
|
|
594
|
+
) || this.expressionUsesDescriptorTable(
|
|
595
|
+
info.right,
|
|
596
|
+
definitions,
|
|
597
|
+
visitingLocals,
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
if (info.id === binaryen.SelectId) {
|
|
601
|
+
return this.expressionUsesDescriptorTable(
|
|
602
|
+
info.condition,
|
|
603
|
+
definitions,
|
|
604
|
+
visitingLocals,
|
|
605
|
+
) || this.expressionUsesDescriptorTable(
|
|
606
|
+
info.ifTrue,
|
|
607
|
+
definitions,
|
|
608
|
+
visitingLocals,
|
|
609
|
+
) || this.expressionUsesDescriptorTable(
|
|
610
|
+
info.ifFalse,
|
|
611
|
+
definitions,
|
|
612
|
+
visitingLocals,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
descriptorPointerSideEffects(expression) {
|
|
619
|
+
const result = [];
|
|
620
|
+
const visit = (candidate) => {
|
|
621
|
+
const info = binaryen.getExpressionInfo(candidate);
|
|
622
|
+
if (info.id === binaryen.LocalSetId && info.isTee) {
|
|
623
|
+
result.push(
|
|
624
|
+
this.module.local.set(info.index, this.module.copyExpression(info.value)),
|
|
625
|
+
);
|
|
626
|
+
} else if (info.id === binaryen.UnaryId) {
|
|
627
|
+
visit(info.value);
|
|
628
|
+
} else if (info.id === binaryen.BinaryId) {
|
|
629
|
+
visit(info.left);
|
|
630
|
+
visit(info.right);
|
|
631
|
+
} else if (info.id === binaryen.SelectId) {
|
|
632
|
+
// Pointer selectors generated by this backend are side-effect free.
|
|
633
|
+
// A nested tee would need conditional reconstruction, so leave it to
|
|
634
|
+
// the conservative invariance check instead of moving it here.
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
visit(expression);
|
|
638
|
+
return result;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
expressionContainsTee(expression) {
|
|
642
|
+
const info = binaryen.getExpressionInfo(expression);
|
|
643
|
+
if (info.id === binaryen.LocalSetId) return info.isTee;
|
|
644
|
+
if (info.id === binaryen.UnaryId) {
|
|
645
|
+
return this.expressionContainsTee(info.value);
|
|
646
|
+
}
|
|
647
|
+
if (info.id === binaryen.BinaryId) {
|
|
648
|
+
return this.expressionContainsTee(info.left)
|
|
649
|
+
|| this.expressionContainsTee(info.right);
|
|
650
|
+
}
|
|
651
|
+
if (info.id === binaryen.SelectId) {
|
|
652
|
+
return this.expressionContainsTee(info.condition)
|
|
653
|
+
|| this.expressionContainsTee(info.ifTrue)
|
|
654
|
+
|| this.expressionContainsTee(info.ifFalse);
|
|
655
|
+
}
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
visitExpression(expression, visitor) {
|
|
660
|
+
visitor(expression);
|
|
661
|
+
let complete = true;
|
|
662
|
+
const supported = this.rewriteExpressionChildren(expression, (child) => {
|
|
663
|
+
if (!this.visitExpression(child, visitor)) complete = false;
|
|
664
|
+
return child;
|
|
665
|
+
});
|
|
666
|
+
return complete && supported;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
rewriteExpressionChildren(expression, rewrite) {
|
|
670
|
+
const info = binaryen.getExpressionInfo(expression);
|
|
671
|
+
const replace = (child, setter) => {
|
|
672
|
+
if (child) setter(rewrite(child));
|
|
673
|
+
};
|
|
674
|
+
switch (info.id) {
|
|
675
|
+
case binaryen.BlockId:
|
|
676
|
+
info.children.forEach((child, index) =>
|
|
677
|
+
replace(child, (value) => binaryen.Block.setChildAt(expression, index, value))
|
|
678
|
+
);
|
|
679
|
+
break;
|
|
680
|
+
case binaryen.IfId:
|
|
681
|
+
replace(info.condition, (value) => binaryen.If.setCondition(expression, value));
|
|
682
|
+
replace(info.ifTrue, (value) => binaryen.If.setIfTrue(expression, value));
|
|
683
|
+
replace(info.ifFalse, (value) => binaryen.If.setIfFalse(expression, value));
|
|
684
|
+
break;
|
|
685
|
+
case binaryen.LoopId:
|
|
686
|
+
replace(info.body, (value) => binaryen.Loop.setBody(expression, value));
|
|
687
|
+
break;
|
|
688
|
+
case binaryen.BreakId:
|
|
689
|
+
replace(info.condition, (value) => binaryen.Break.setCondition(expression, value));
|
|
690
|
+
replace(info.value, (value) => binaryen.Break.setValue(expression, value));
|
|
691
|
+
break;
|
|
692
|
+
case binaryen.SwitchId:
|
|
693
|
+
replace(info.condition, (value) => binaryen.Switch.setCondition(expression, value));
|
|
694
|
+
replace(info.value, (value) => binaryen.Switch.setValue(expression, value));
|
|
695
|
+
break;
|
|
696
|
+
case binaryen.CallId:
|
|
697
|
+
info.operands.forEach((child, index) =>
|
|
698
|
+
replace(child, (value) => binaryen.Call.setOperandAt(expression, index, value))
|
|
699
|
+
);
|
|
700
|
+
break;
|
|
701
|
+
case binaryen.CallIndirectId:
|
|
702
|
+
replace(info.target, (value) => binaryen.CallIndirect.setTarget(expression, value));
|
|
703
|
+
info.operands.forEach((child, index) =>
|
|
704
|
+
replace(child, (value) => binaryen.CallIndirect.setOperandAt(expression, index, value))
|
|
705
|
+
);
|
|
706
|
+
break;
|
|
707
|
+
case binaryen.LocalSetId:
|
|
708
|
+
replace(info.value, (value) => binaryen.LocalSet.setValue(expression, value));
|
|
709
|
+
break;
|
|
710
|
+
case binaryen.GlobalSetId:
|
|
711
|
+
replace(info.value, (value) => binaryen.GlobalSet.setValue(expression, value));
|
|
712
|
+
break;
|
|
713
|
+
case binaryen.LoadId:
|
|
714
|
+
replace(info.ptr, (value) => binaryen.Load.setPtr(expression, value));
|
|
715
|
+
break;
|
|
716
|
+
case binaryen.StoreId:
|
|
717
|
+
replace(info.ptr, (value) => binaryen.Store.setPtr(expression, value));
|
|
718
|
+
replace(info.value, (value) => binaryen.Store.setValue(expression, value));
|
|
719
|
+
break;
|
|
720
|
+
case binaryen.UnaryId:
|
|
721
|
+
replace(info.value, (value) => binaryen.Unary.setValue(expression, value));
|
|
722
|
+
break;
|
|
723
|
+
case binaryen.BinaryId:
|
|
724
|
+
replace(info.left, (value) => binaryen.Binary.setLeft(expression, value));
|
|
725
|
+
replace(info.right, (value) => binaryen.Binary.setRight(expression, value));
|
|
726
|
+
break;
|
|
727
|
+
case binaryen.SelectId:
|
|
728
|
+
replace(info.ifTrue, (value) => binaryen.Select.setIfTrue(expression, value));
|
|
729
|
+
replace(info.ifFalse, (value) => binaryen.Select.setIfFalse(expression, value));
|
|
730
|
+
replace(info.condition, (value) => binaryen.Select.setCondition(expression, value));
|
|
731
|
+
break;
|
|
732
|
+
case binaryen.DropId:
|
|
733
|
+
replace(info.value, (value) => binaryen.Drop.setValue(expression, value));
|
|
734
|
+
break;
|
|
735
|
+
case binaryen.ReturnId:
|
|
736
|
+
replace(info.value, (value) => binaryen.Return.setValue(expression, value));
|
|
737
|
+
break;
|
|
738
|
+
case binaryen.MemoryCopyId:
|
|
739
|
+
replace(info.dest, (value) => binaryen.MemoryCopy.setDest(expression, value));
|
|
740
|
+
replace(info.source, (value) => binaryen.MemoryCopy.setSource(expression, value));
|
|
741
|
+
replace(info.size, (value) => binaryen.MemoryCopy.setSize(expression, value));
|
|
742
|
+
break;
|
|
743
|
+
case binaryen.MemoryFillId:
|
|
744
|
+
replace(info.dest, (value) => binaryen.MemoryFill.setDest(expression, value));
|
|
745
|
+
replace(info.value, (value) => binaryen.MemoryFill.setValue(expression, value));
|
|
746
|
+
replace(info.size, (value) => binaryen.MemoryFill.setSize(expression, value));
|
|
747
|
+
break;
|
|
748
|
+
case binaryen.SIMDExtractId:
|
|
749
|
+
replace(info.vec, (value) => binaryen.SIMDExtract.setVec(expression, value));
|
|
750
|
+
break;
|
|
751
|
+
case binaryen.SIMDReplaceId:
|
|
752
|
+
replace(info.vec, (value) => binaryen.SIMDReplace.setVec(expression, value));
|
|
753
|
+
replace(info.value, (value) => binaryen.SIMDReplace.setValue(expression, value));
|
|
754
|
+
break;
|
|
755
|
+
case binaryen.SIMDShuffleId:
|
|
756
|
+
replace(info.left, (value) => binaryen.SIMDShuffle.setLeft(expression, value));
|
|
757
|
+
replace(info.right, (value) => binaryen.SIMDShuffle.setRight(expression, value));
|
|
758
|
+
break;
|
|
759
|
+
case binaryen.SIMDTernaryId:
|
|
760
|
+
replace(info.a, (value) => binaryen.SIMDTernary.setA(expression, value));
|
|
761
|
+
replace(info.b, (value) => binaryen.SIMDTernary.setB(expression, value));
|
|
762
|
+
replace(info.c, (value) => binaryen.SIMDTernary.setC(expression, value));
|
|
763
|
+
break;
|
|
764
|
+
case binaryen.SIMDShiftId:
|
|
765
|
+
replace(info.vec, (value) => binaryen.SIMDShift.setVec(expression, value));
|
|
766
|
+
replace(info.shift, (value) => binaryen.SIMDShift.setShift(expression, value));
|
|
767
|
+
break;
|
|
768
|
+
case binaryen.SIMDLoadId:
|
|
769
|
+
replace(info.ptr, (value) => binaryen.SIMDLoad.setPtr(expression, value));
|
|
770
|
+
break;
|
|
771
|
+
case binaryen.SIMDLoadStoreLaneId:
|
|
772
|
+
replace(info.ptr, (value) => binaryen.SIMDLoadStoreLane.setPtr(expression, value));
|
|
773
|
+
replace(info.vec, (value) => binaryen.SIMDLoadStoreLane.setVec(expression, value));
|
|
774
|
+
break;
|
|
775
|
+
case binaryen.TupleMakeId:
|
|
776
|
+
info.operands.forEach((child, index) =>
|
|
777
|
+
replace(child, (value) => binaryen.TupleMake.setOperandAt(expression, index, value))
|
|
778
|
+
);
|
|
779
|
+
break;
|
|
780
|
+
case binaryen.TupleExtractId:
|
|
781
|
+
replace(info.tuple, (value) => binaryen.TupleExtract.setTuple(expression, value));
|
|
782
|
+
break;
|
|
783
|
+
case binaryen.ConstId:
|
|
784
|
+
case binaryen.LocalGetId:
|
|
785
|
+
case binaryen.GlobalGetId:
|
|
786
|
+
case binaryen.NopId:
|
|
787
|
+
case binaryen.UnreachableId:
|
|
788
|
+
case binaryen.MemorySizeId:
|
|
789
|
+
case binaryen.DataDropId:
|
|
790
|
+
break;
|
|
791
|
+
default:
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
|
|
247
797
|
validateEnvelope() {
|
|
248
798
|
const mir = this.mir;
|
|
249
799
|
if (!mir || typeof mir !== "object" || Array.isArray(mir)) {
|
|
@@ -303,7 +853,127 @@ class MirCompiler {
|
|
|
303
853
|
this.validateCurrentSchemaEnvelope();
|
|
304
854
|
this.validateProcessEntrySignature();
|
|
305
855
|
this.validateAcyclicCallGraph();
|
|
856
|
+
this.analyzeBufferWrites();
|
|
306
857
|
this.analyzeRecoverableFailures();
|
|
858
|
+
this.analyzeScalarReferenceParameters();
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
analyzeScalarReferenceParameters() {
|
|
862
|
+
// MIR reference modes are conservative. Internal scalar references that
|
|
863
|
+
// are never written, never escape to a writable reference, and never alias
|
|
864
|
+
// a writable argument can safely use a value ABI. This removes scratch
|
|
865
|
+
// memory traffic and exposes their values to post-inlining loop analysis.
|
|
866
|
+
const candidates = this.mir.functions.map((func) =>
|
|
867
|
+
func.params.map((parameter) => {
|
|
868
|
+
const type = this.type(parameter.ty);
|
|
869
|
+
return type.kind === "scalar" && parameter.mode !== "value";
|
|
870
|
+
})
|
|
871
|
+
);
|
|
872
|
+
const callSites = this.mir.functions.map(() => []);
|
|
873
|
+
|
|
874
|
+
const visitBlock = (functionId, block) => {
|
|
875
|
+
for (const statement of block.statements) {
|
|
876
|
+
const kind = statement.kind?.kind;
|
|
877
|
+
const data = statement.kind?.data;
|
|
878
|
+
if (
|
|
879
|
+
kind === "assign"
|
|
880
|
+
&& data.destination?.base?.kind === "parameter"
|
|
881
|
+
&& data.destination.projections.length === 0
|
|
882
|
+
) {
|
|
883
|
+
candidates[functionId][data.destination.base.data] = false;
|
|
884
|
+
} else if (kind === "call") {
|
|
885
|
+
callSites[data.function].push({ caller: functionId, call: data });
|
|
886
|
+
} else if (kind === "if") {
|
|
887
|
+
visitBlock(functionId, data.then_block);
|
|
888
|
+
visitBlock(functionId, data.else_block);
|
|
889
|
+
} else if (kind === "loop") {
|
|
890
|
+
visitBlock(functionId, data.body);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
for (const [functionId, func] of this.mir.functions.entries()) {
|
|
895
|
+
visitBlock(functionId, func.body);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const unprojectedScalarPlace = (argument, functionId) => {
|
|
899
|
+
if (
|
|
900
|
+
argument?.kind !== "place"
|
|
901
|
+
|| argument.data.projections.length !== 0
|
|
902
|
+
|| !["local", "parameter"].includes(argument.data.base.kind)
|
|
903
|
+
) {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
const typeId = argument.data.base.kind === "local"
|
|
907
|
+
? this.mir.functions[functionId].locals[argument.data.base.data]?.ty
|
|
908
|
+
: this.mir.functions[functionId].params[argument.data.base.data]?.ty;
|
|
909
|
+
return Number.isInteger(typeId) && this.type(typeId).kind === "scalar"
|
|
910
|
+
? argument.data.base
|
|
911
|
+
: null;
|
|
912
|
+
};
|
|
913
|
+
const sameBase = (lhs, rhs) =>
|
|
914
|
+
lhs?.kind === rhs?.kind && lhs?.data === rhs?.data;
|
|
915
|
+
|
|
916
|
+
let changed = true;
|
|
917
|
+
while (changed) {
|
|
918
|
+
changed = false;
|
|
919
|
+
for (const [calleeId, sites] of callSites.entries()) {
|
|
920
|
+
for (const { caller: callerId, call } of sites) {
|
|
921
|
+
for (let parameterId = 0; parameterId < candidates[calleeId].length; parameterId += 1) {
|
|
922
|
+
if (!candidates[calleeId][parameterId]) continue;
|
|
923
|
+
const base = unprojectedScalarPlace(call.args[parameterId], callerId);
|
|
924
|
+
const forwardedCandidate =
|
|
925
|
+
base?.kind !== "parameter"
|
|
926
|
+
|| candidates[callerId][base.data];
|
|
927
|
+
const aliasesWritableArgument = call.args.some((argument, index) => {
|
|
928
|
+
if (index === parameterId || candidates[calleeId][index]) return false;
|
|
929
|
+
return sameBase(
|
|
930
|
+
base,
|
|
931
|
+
unprojectedScalarPlace(argument, callerId),
|
|
932
|
+
);
|
|
933
|
+
});
|
|
934
|
+
if (!base || !forwardedCandidate || aliasesWritableArgument) {
|
|
935
|
+
candidates[calleeId][parameterId] = false;
|
|
936
|
+
changed = true;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
for (const [callerId, func] of this.mir.functions.entries()) {
|
|
943
|
+
const visitCalls = (block) => {
|
|
944
|
+
for (const statement of block.statements) {
|
|
945
|
+
const kind = statement.kind?.kind;
|
|
946
|
+
const data = statement.kind?.data;
|
|
947
|
+
if (kind === "call") {
|
|
948
|
+
data.args.forEach((argument, parameterId) => {
|
|
949
|
+
const base = unprojectedScalarPlace(argument, callerId);
|
|
950
|
+
if (
|
|
951
|
+
base?.kind === "parameter"
|
|
952
|
+
&& candidates[callerId][base.data]
|
|
953
|
+
&& !candidates[data.function][parameterId]
|
|
954
|
+
) {
|
|
955
|
+
candidates[callerId][base.data] = false;
|
|
956
|
+
changed = true;
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
} else if (kind === "if") {
|
|
960
|
+
visitCalls(data.then_block);
|
|
961
|
+
visitCalls(data.else_block);
|
|
962
|
+
} else if (kind === "loop") {
|
|
963
|
+
visitCalls(data.body);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
visitCalls(func.body);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
this.scalarParameterByValue = candidates;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
parameterPassingMode(functionId, parameterId) {
|
|
974
|
+
return this.scalarParameterByValue[functionId]?.[parameterId]
|
|
975
|
+
? "value"
|
|
976
|
+
: this.mir.functions[functionId].params[parameterId].mode;
|
|
307
977
|
}
|
|
308
978
|
|
|
309
979
|
validateCurrentSchemaEnvelope() {
|
|
@@ -451,90 +1121,470 @@ class MirCompiler {
|
|
|
451
1121
|
}
|
|
452
1122
|
}
|
|
453
1123
|
|
|
454
|
-
|
|
455
|
-
const
|
|
456
|
-
const
|
|
457
|
-
const
|
|
1124
|
+
analyzeBufferWrites() {
|
|
1125
|
+
const bufferOrigin = (id) => `buffer:${id}`;
|
|
1126
|
+
const parameterOrigin = (id, slot = 0) => `parameter:${id}:${slot}`;
|
|
1127
|
+
const selectedSlots = (selector, len, bounds) => {
|
|
1128
|
+
if (!Number.isInteger(len) || len <= 0) return [];
|
|
458
1129
|
if (
|
|
459
|
-
|
|
460
|
-
|
|
1130
|
+
selector?.kind === "constant"
|
|
1131
|
+
&& selector.data?.type === "i32"
|
|
1132
|
+
&& Number.isInteger(selector.data.value)
|
|
461
1133
|
) {
|
|
462
|
-
|
|
1134
|
+
let slot = selector.data.value;
|
|
1135
|
+
if (bounds === "clamp") {
|
|
1136
|
+
slot = Math.min(len - 1, Math.max(0, slot));
|
|
1137
|
+
return [slot];
|
|
1138
|
+
}
|
|
1139
|
+
if (slot >= 0 && slot < len) return [slot];
|
|
463
1140
|
}
|
|
464
|
-
|
|
465
|
-
const scalar = lhs.kind === "constant"
|
|
466
|
-
? lhs.data.type
|
|
467
|
-
: this.requireScalarType(
|
|
468
|
-
this.mir.functions[functionId].locals[lhs.data].ty,
|
|
469
|
-
`binary operand in '${this.mir.functions[functionId].name}'`,
|
|
470
|
-
);
|
|
471
|
-
return scalar === "i32" || scalar === "i64";
|
|
1141
|
+
return Array.from({ length: len }, (_, slot) => slot);
|
|
472
1142
|
};
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
"
|
|
476
|
-
|
|
477
|
-
"buffer_param_store",
|
|
478
|
-
"buffer_store",
|
|
479
|
-
"const_data_load",
|
|
480
|
-
"index",
|
|
481
|
-
"input_load",
|
|
482
|
-
"make_slice",
|
|
483
|
-
"output_load",
|
|
484
|
-
"output_store",
|
|
485
|
-
"control_output_store",
|
|
486
|
-
]);
|
|
487
|
-
const dynamicBoundsKinds = new Set([
|
|
488
|
-
"slice_element",
|
|
489
|
-
"slice_load",
|
|
490
|
-
"slice_store",
|
|
491
|
-
"slice_window",
|
|
492
|
-
]);
|
|
493
|
-
const scan = (functionId, value) => {
|
|
494
|
-
if (value === null || value === undefined) return;
|
|
495
|
-
if (Array.isArray(value)) {
|
|
496
|
-
for (const entry of value) scan(functionId, entry);
|
|
497
|
-
return;
|
|
1143
|
+
const bufferIds = (bufferRef) => {
|
|
1144
|
+
if (Number.isInteger(bufferRef)) return [bufferRef];
|
|
1145
|
+
if (bufferRef?.kind === "direct" && Number.isInteger(bufferRef.data)) {
|
|
1146
|
+
return [bufferRef.data];
|
|
498
1147
|
}
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
|
|
1148
|
+
if (
|
|
1149
|
+
bufferRef?.kind === "array_element"
|
|
1150
|
+
&& Number.isInteger(bufferRef.data?.first)
|
|
1151
|
+
&& Number.isInteger(bufferRef.data?.len)
|
|
1152
|
+
&& bufferRef.data.len > 0
|
|
1153
|
+
) {
|
|
1154
|
+
return selectedSlots(
|
|
1155
|
+
bufferRef.data.selector,
|
|
1156
|
+
bufferRef.data.len,
|
|
1157
|
+
bufferRef.data.bounds,
|
|
1158
|
+
).map(
|
|
1159
|
+
(slot) => bufferRef.data.first + slot,
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
return [];
|
|
1163
|
+
};
|
|
1164
|
+
const bufferOrigins = (bufferRef) =>
|
|
1165
|
+
new Set(bufferIds(bufferRef).map(bufferOrigin));
|
|
1166
|
+
const bufferParamSlots = (parameterRef, func) => {
|
|
1167
|
+
if (Number.isInteger(parameterRef)) {
|
|
1168
|
+
return [{ parameter: parameterRef, slot: 0 }];
|
|
502
1169
|
}
|
|
503
|
-
const bounds = value.data?.bounds;
|
|
504
1170
|
if (
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|| binaryMayFail(functionId, value)
|
|
508
|
-
|| (checkedBoundsKinds.has(value.kind) && bounds === "checked")
|
|
509
|
-
|| (dynamicBoundsKinds.has(value.kind) && bounds !== "unchecked")
|
|
1171
|
+
parameterRef?.kind === "direct"
|
|
1172
|
+
&& Number.isInteger(parameterRef.data)
|
|
510
1173
|
) {
|
|
511
|
-
|
|
1174
|
+
return [{ parameter: parameterRef.data, slot: 0 }];
|
|
512
1175
|
}
|
|
513
|
-
|
|
1176
|
+
if (
|
|
1177
|
+
parameterRef?.kind === "array_element"
|
|
1178
|
+
&& Number.isInteger(parameterRef.data?.span)
|
|
1179
|
+
) {
|
|
1180
|
+
const parameter = func.params?.[parameterRef.data.span];
|
|
1181
|
+
const type = parameter && this.type(parameter.ty);
|
|
1182
|
+
const len = type?.kind === "buffer_span" ? type.data.len : 1;
|
|
1183
|
+
return selectedSlots(
|
|
1184
|
+
parameterRef.data.selector,
|
|
1185
|
+
len,
|
|
1186
|
+
parameterRef.data.bounds,
|
|
1187
|
+
).map((slot) => ({ parameter: parameterRef.data.span, slot }));
|
|
1188
|
+
}
|
|
1189
|
+
return [];
|
|
514
1190
|
};
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
1191
|
+
const bufferParamOrigins = (parameterRef, func) => new Set(
|
|
1192
|
+
bufferParamSlots(parameterRef, func).map(({ parameter, slot }) =>
|
|
1193
|
+
parameterOrigin(parameter, slot)
|
|
1194
|
+
),
|
|
1195
|
+
);
|
|
1196
|
+
const localId = (value) =>
|
|
1197
|
+
value?.kind === "local" && Number.isInteger(value.data)
|
|
1198
|
+
? value.data
|
|
1199
|
+
: null;
|
|
1200
|
+
const setEquals = (lhs, rhs) =>
|
|
1201
|
+
lhs.size === rhs.size && [...lhs].every((entry) => rhs.has(entry));
|
|
1202
|
+
const summaryEquals = (lhs, rhs) =>
|
|
1203
|
+
setEquals(lhs.buffers, rhs.buffers)
|
|
1204
|
+
&& setEquals(lhs.parameters, rhs.parameters);
|
|
1205
|
+
|
|
1206
|
+
const valueOrigins = (value, aliases) => {
|
|
1207
|
+
const id = localId(value);
|
|
1208
|
+
return id === null ? new Set() : new Set(aliases[id] ?? []);
|
|
1209
|
+
};
|
|
1210
|
+
const placeOrigins = (place, aliases) => {
|
|
1211
|
+
const base = place?.base;
|
|
1212
|
+
if (base?.kind === "parameter" && Number.isInteger(base.data)) {
|
|
1213
|
+
return new Set([parameterOrigin(base.data, 0)]);
|
|
530
1214
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
1215
|
+
if (base?.kind === "local" && Number.isInteger(base.data)) {
|
|
1216
|
+
return new Set(aliases[base.data] ?? []);
|
|
1217
|
+
}
|
|
1218
|
+
return new Set();
|
|
1219
|
+
};
|
|
1220
|
+
const rvalueOrigins = (value, aliases, func) => {
|
|
1221
|
+
if (value?.kind === "use") {
|
|
1222
|
+
return valueOrigins(value.data, aliases);
|
|
1223
|
+
}
|
|
1224
|
+
if (value?.kind === "load") {
|
|
1225
|
+
return placeOrigins(value.data, aliases);
|
|
1226
|
+
}
|
|
1227
|
+
if (value?.kind !== "make_slice") {
|
|
1228
|
+
return new Set();
|
|
1229
|
+
}
|
|
1230
|
+
const source = value.data?.source;
|
|
1231
|
+
if (source?.kind === "buffer") {
|
|
1232
|
+
return bufferOrigins(source.data?.buffer);
|
|
1233
|
+
}
|
|
1234
|
+
if (source?.kind === "buffer_param") {
|
|
1235
|
+
return bufferParamOrigins(source.data?.parameter, func);
|
|
1236
|
+
}
|
|
1237
|
+
if (source?.kind === "place") {
|
|
1238
|
+
return placeOrigins(source.data, aliases);
|
|
1239
|
+
}
|
|
1240
|
+
return new Set();
|
|
1241
|
+
};
|
|
1242
|
+
const collectAliases = (func) => {
|
|
1243
|
+
const aliases = (func.locals ?? []).map(() => new Set());
|
|
1244
|
+
let changed = true;
|
|
1245
|
+
const visitBlock = (block) => {
|
|
1246
|
+
for (const statement of block?.statements ?? []) {
|
|
1247
|
+
const kind = statement.kind?.kind;
|
|
1248
|
+
const data = statement.kind?.data;
|
|
1249
|
+
if (
|
|
1250
|
+
kind === "assign"
|
|
1251
|
+
&& data?.destination?.projections?.length === 0
|
|
1252
|
+
&& data.destination.base?.kind === "local"
|
|
1253
|
+
) {
|
|
1254
|
+
const destination = data.destination.base.data;
|
|
1255
|
+
const origins = rvalueOrigins(data.value, aliases, func);
|
|
1256
|
+
for (const origin of origins) {
|
|
1257
|
+
if (!aliases[destination].has(origin)) {
|
|
1258
|
+
aliases[destination].add(origin);
|
|
1259
|
+
changed = true;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
} else if (kind === "if") {
|
|
1263
|
+
visitBlock(data?.then_block);
|
|
1264
|
+
visitBlock(data?.else_block);
|
|
1265
|
+
} else if (kind === "loop") {
|
|
1266
|
+
visitBlock(data?.body);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
while (changed) {
|
|
1271
|
+
changed = false;
|
|
1272
|
+
visitBlock(func.body);
|
|
1273
|
+
}
|
|
1274
|
+
return aliases;
|
|
1275
|
+
};
|
|
1276
|
+
const collectUnsupportedResults = (func) => {
|
|
1277
|
+
const results = new Set();
|
|
1278
|
+
const visitBlock = (block) => {
|
|
1279
|
+
for (const statement of block?.statements ?? []) {
|
|
1280
|
+
const kind = statement.kind?.kind;
|
|
1281
|
+
const data = statement.kind?.data;
|
|
1282
|
+
if (kind === "call") {
|
|
1283
|
+
for (const result of data?.results ?? []) {
|
|
1284
|
+
const type = this.mir.types[func.locals?.[result]?.ty];
|
|
1285
|
+
if (type?.kind === "slice" || type?.kind === "buffer") {
|
|
1286
|
+
results.add(result);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
} else if (kind === "if") {
|
|
1290
|
+
visitBlock(data?.then_block);
|
|
1291
|
+
visitBlock(data?.else_block);
|
|
1292
|
+
} else if (kind === "loop") {
|
|
1293
|
+
visitBlock(data?.body);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
visitBlock(func.body);
|
|
1298
|
+
return results;
|
|
1299
|
+
};
|
|
1300
|
+
const argumentValue = (argument) => {
|
|
1301
|
+
switch (argument?.kind) {
|
|
1302
|
+
case "value": return argument.data;
|
|
1303
|
+
case "slice_element":
|
|
1304
|
+
case "slice_window": return argument.data?.slice;
|
|
1305
|
+
default: return null;
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
const argumentUsesUnsupportedResult = (argument, unsupported) => {
|
|
1309
|
+
const value = argumentValue(argument);
|
|
1310
|
+
const valueLocal = localId(value);
|
|
1311
|
+
if (valueLocal !== null) {
|
|
1312
|
+
return unsupported.has(valueLocal);
|
|
1313
|
+
}
|
|
1314
|
+
if (argument?.kind === "place") {
|
|
1315
|
+
const base = argument.data?.base;
|
|
1316
|
+
return base?.kind === "local" && unsupported.has(base.data);
|
|
1317
|
+
}
|
|
1318
|
+
if (argument?.kind === "array_window") {
|
|
1319
|
+
const base = argument.data?.array?.base;
|
|
1320
|
+
return base?.kind === "local" && unsupported.has(base.data);
|
|
1321
|
+
}
|
|
1322
|
+
return false;
|
|
1323
|
+
};
|
|
1324
|
+
const argumentOrigins = (argument, aliases, func, slot) => {
|
|
1325
|
+
switch (argument?.kind) {
|
|
1326
|
+
case "buffer":
|
|
1327
|
+
return bufferOrigins(argument.data);
|
|
1328
|
+
case "buffer_param":
|
|
1329
|
+
return bufferParamOrigins(argument.data, func);
|
|
1330
|
+
case "buffer_span": {
|
|
1331
|
+
const span = argument.data;
|
|
1332
|
+
if (span?.kind === "interface") {
|
|
1333
|
+
const len = span.data?.len ?? 0;
|
|
1334
|
+
if (Number.isInteger(slot) && slot >= 0 && slot < len) {
|
|
1335
|
+
return new Set([bufferOrigin(span.data.first + slot)]);
|
|
1336
|
+
}
|
|
1337
|
+
return new Set(Array.from({ length: len }, (_, index) =>
|
|
1338
|
+
bufferOrigin(span.data.first + index)
|
|
1339
|
+
));
|
|
1340
|
+
}
|
|
1341
|
+
if (span?.kind === "parameter") {
|
|
1342
|
+
const start = span.data?.start ?? 0;
|
|
1343
|
+
const len = span.data?.len ?? 0;
|
|
1344
|
+
if (Number.isInteger(slot) && slot >= 0 && slot < len) {
|
|
1345
|
+
return new Set([parameterOrigin(span.data.span, start + slot)]);
|
|
1346
|
+
}
|
|
1347
|
+
return new Set(Array.from({ length: len }, (_, index) =>
|
|
1348
|
+
parameterOrigin(span.data.span, start + index)
|
|
1349
|
+
));
|
|
1350
|
+
}
|
|
1351
|
+
return new Set();
|
|
1352
|
+
}
|
|
1353
|
+
case "place":
|
|
1354
|
+
return placeOrigins(argument.data, aliases);
|
|
1355
|
+
case "array_window":
|
|
1356
|
+
return placeOrigins(argument.data?.array, aliases);
|
|
1357
|
+
case "value":
|
|
1358
|
+
return valueOrigins(argument.data, aliases);
|
|
1359
|
+
case "slice_element":
|
|
1360
|
+
case "slice_window":
|
|
1361
|
+
return valueOrigins(argument.data?.slice, aliases);
|
|
1362
|
+
default:
|
|
1363
|
+
return new Set();
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
const markOrigins = (origins, summary) => {
|
|
1367
|
+
for (const origin of origins) {
|
|
1368
|
+
const [kind, idText] = origin.split(":");
|
|
1369
|
+
const id = Number(idText);
|
|
1370
|
+
if (kind === "buffer") {
|
|
1371
|
+
summary.buffers.add(id);
|
|
1372
|
+
} else if (kind === "parameter") {
|
|
1373
|
+
summary.parameters.add(origin);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1378
|
+
const aliases = this.mir.functions.map(collectAliases);
|
|
1379
|
+
const unsupported = this.mir.functions.map(collectUnsupportedResults);
|
|
1380
|
+
let summaries = this.mir.functions.map(() => ({
|
|
1381
|
+
buffers: new Set(),
|
|
1382
|
+
parameters: new Set(),
|
|
1383
|
+
}));
|
|
1384
|
+
while (true) {
|
|
1385
|
+
const next = this.mir.functions.map((func, functionId) => {
|
|
1386
|
+
const summary = { buffers: new Set(), parameters: new Set() };
|
|
1387
|
+
const markValueWrite = (value, description) => {
|
|
1388
|
+
const id = localId(value);
|
|
1389
|
+
if (id !== null && unsupported[functionId].has(id)) {
|
|
1390
|
+
this.fail(
|
|
1391
|
+
`cannot infer interface-buffer writes for ${description} through a slice returned by a MIR call`,
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
markOrigins(valueOrigins(value, aliases[functionId]), summary);
|
|
1395
|
+
};
|
|
1396
|
+
const visitBlock = (block) => {
|
|
1397
|
+
for (const statement of block?.statements ?? []) {
|
|
1398
|
+
const kind = statement.kind?.kind;
|
|
1399
|
+
const data = statement.kind?.data;
|
|
1400
|
+
if (
|
|
1401
|
+
kind === "assign"
|
|
1402
|
+
&& data?.destination?.base?.kind === "parameter"
|
|
1403
|
+
) {
|
|
1404
|
+
summary.parameters.add(parameterOrigin(data.destination.base.data, 0));
|
|
1405
|
+
} else if (kind === "buffer_store") {
|
|
1406
|
+
for (const buffer of bufferIds(data?.buffer)) {
|
|
1407
|
+
summary.buffers.add(buffer);
|
|
1408
|
+
}
|
|
1409
|
+
} else if (kind === "buffer_param_store") {
|
|
1410
|
+
markOrigins(
|
|
1411
|
+
bufferParamOrigins(data?.parameter, func),
|
|
1412
|
+
summary,
|
|
1413
|
+
);
|
|
1414
|
+
} else if (kind === "slice_store") {
|
|
1415
|
+
markValueWrite(data?.slice, "slice store");
|
|
1416
|
+
} else if (kind === "slice_fill" || kind === "slice_copy") {
|
|
1417
|
+
markValueWrite(data?.destination, "slice write");
|
|
1418
|
+
} else if (kind === "call") {
|
|
1419
|
+
const callee = summaries[data?.function];
|
|
1420
|
+
if (!callee) {
|
|
1421
|
+
this.fail(
|
|
1422
|
+
`MIR call references missing function ${String(data?.function)}`,
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
for (const buffer of callee.buffers) {
|
|
1426
|
+
summary.buffers.add(buffer);
|
|
1427
|
+
}
|
|
1428
|
+
for (const parameterOriginValue of callee.parameters) {
|
|
1429
|
+
const [, parameterText, slotText] = parameterOriginValue.split(":");
|
|
1430
|
+
const parameter = Number(parameterText);
|
|
1431
|
+
const slot = Number(slotText);
|
|
1432
|
+
const argument = data?.args?.[parameter];
|
|
1433
|
+
if (!argument) {
|
|
1434
|
+
this.fail(
|
|
1435
|
+
`MIR call to function ${data.function} has no argument for writable parameter ${parameter}`,
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
if (
|
|
1439
|
+
argumentUsesUnsupportedResult(
|
|
1440
|
+
argument,
|
|
1441
|
+
unsupported[functionId],
|
|
1442
|
+
)
|
|
1443
|
+
) {
|
|
1444
|
+
this.fail(
|
|
1445
|
+
"cannot infer interface-buffer writes through a slice or buffer returned by a MIR call",
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
markOrigins(
|
|
1449
|
+
argumentOrigins(argument, aliases[functionId], func, slot),
|
|
1450
|
+
summary,
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
} else if (kind === "if") {
|
|
1454
|
+
visitBlock(data?.then_block);
|
|
1455
|
+
visitBlock(data?.else_block);
|
|
1456
|
+
} else if (kind === "loop") {
|
|
1457
|
+
visitBlock(data?.body);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
visitBlock(func.body);
|
|
1462
|
+
return summary;
|
|
1463
|
+
});
|
|
1464
|
+
if (next.every((summary, index) => summaryEquals(summary, summaries[index]))) {
|
|
1465
|
+
summaries = next;
|
|
1466
|
+
break;
|
|
1467
|
+
}
|
|
1468
|
+
summaries = next;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
const roots = [
|
|
1472
|
+
this.mir.entry_points.init,
|
|
1473
|
+
this.mir.entry_points.process,
|
|
1474
|
+
...this.mir.interface.events.map((event) => event.handler),
|
|
1475
|
+
];
|
|
1476
|
+
this.bufferMayWrite = this.mir.interface.buffers.map(() => false);
|
|
1477
|
+
for (const root of roots) {
|
|
1478
|
+
const summary = summaries[root];
|
|
1479
|
+
if (!summary) {
|
|
1480
|
+
this.fail(`MIR buffer-write root function ${String(root)} is missing`);
|
|
1481
|
+
}
|
|
1482
|
+
for (const bufferId of summary.buffers) {
|
|
1483
|
+
if (
|
|
1484
|
+
!Number.isInteger(bufferId)
|
|
1485
|
+
|| bufferId < 0
|
|
1486
|
+
|| bufferId >= this.bufferMayWrite.length
|
|
1487
|
+
) {
|
|
1488
|
+
this.fail(
|
|
1489
|
+
`MIR buffer-write analysis references missing buffer ${String(bufferId)}`,
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
this.bufferMayWrite[bufferId] = true;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
for (const [bufferId, mayWrite] of this.bufferMayWrite.entries()) {
|
|
1496
|
+
if (mayWrite && this.mir.interface.buffers[bufferId].access !== "read_write") {
|
|
1497
|
+
this.fail(
|
|
1498
|
+
`MIR writes read-only interface buffer '${this.mir.interface.buffers[bufferId].name}'`,
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
analyzeRecoverableFailures() {
|
|
1505
|
+
const callees = this.mir.functions.map(() => new Set());
|
|
1506
|
+
const direct = this.mir.functions.map(() => false);
|
|
1507
|
+
const binaryMayFail = (functionId, value) => {
|
|
1508
|
+
if (
|
|
1509
|
+
value.kind !== "binary"
|
|
1510
|
+
|| !["divide", "remainder"].includes(value.data?.op)
|
|
1511
|
+
) {
|
|
1512
|
+
return false;
|
|
1513
|
+
}
|
|
1514
|
+
const lhs = value.data.lhs;
|
|
1515
|
+
const scalar = lhs.kind === "constant"
|
|
1516
|
+
? lhs.data.type
|
|
1517
|
+
: this.requireScalarType(
|
|
1518
|
+
this.mir.functions[functionId].locals[lhs.data].ty,
|
|
1519
|
+
`binary operand in '${this.mir.functions[functionId].name}'`,
|
|
1520
|
+
);
|
|
1521
|
+
return scalar === "i32" || scalar === "i64";
|
|
1522
|
+
};
|
|
1523
|
+
const checkedBoundsKinds = new Set([
|
|
1524
|
+
"array_window",
|
|
1525
|
+
"buffer_load",
|
|
1526
|
+
"buffer_param_load",
|
|
1527
|
+
"buffer_param_store",
|
|
1528
|
+
"buffer_store",
|
|
1529
|
+
"const_data_load",
|
|
1530
|
+
"index",
|
|
1531
|
+
"input_load",
|
|
1532
|
+
"make_slice",
|
|
1533
|
+
"output_load",
|
|
1534
|
+
"output_store",
|
|
1535
|
+
"control_output_store",
|
|
1536
|
+
]);
|
|
1537
|
+
const dynamicBoundsKinds = new Set([
|
|
1538
|
+
"slice_element",
|
|
1539
|
+
"slice_load",
|
|
1540
|
+
"slice_store",
|
|
1541
|
+
"slice_window",
|
|
1542
|
+
]);
|
|
1543
|
+
const scan = (functionId, value) => {
|
|
1544
|
+
if (value === null || value === undefined) return;
|
|
1545
|
+
if (Array.isArray(value)) {
|
|
1546
|
+
for (const entry of value) scan(functionId, entry);
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
if (typeof value !== "object") return;
|
|
1550
|
+
if (value.kind === "call" && Number.isInteger(value.data?.function)) {
|
|
1551
|
+
callees[functionId].add(value.data.function);
|
|
1552
|
+
}
|
|
1553
|
+
const bounds = value.data?.bounds;
|
|
1554
|
+
if (
|
|
1555
|
+
value.kind === "process_frame"
|
|
1556
|
+
|| value.kind === "slice_copy"
|
|
1557
|
+
|| binaryMayFail(functionId, value)
|
|
1558
|
+
|| (checkedBoundsKinds.has(value.kind) && bounds === "checked")
|
|
1559
|
+
|| (dynamicBoundsKinds.has(value.kind) && bounds !== "unchecked")
|
|
1560
|
+
) {
|
|
1561
|
+
direct[functionId] = true;
|
|
1562
|
+
}
|
|
1563
|
+
for (const child of Object.values(value)) scan(functionId, child);
|
|
1564
|
+
};
|
|
1565
|
+
for (const [functionId, func] of this.mir.functions.entries()) {
|
|
1566
|
+
scan(functionId, func.body);
|
|
1567
|
+
}
|
|
1568
|
+
this.functionMayFail = [...direct];
|
|
1569
|
+
let changed = true;
|
|
1570
|
+
while (changed) {
|
|
1571
|
+
changed = false;
|
|
1572
|
+
for (const [functionId, targets] of callees.entries()) {
|
|
1573
|
+
if (
|
|
1574
|
+
!this.functionMayFail[functionId]
|
|
1575
|
+
&& [...targets].some((target) => this.functionMayFail[target])
|
|
1576
|
+
) {
|
|
1577
|
+
this.functionMayFail[functionId] = true;
|
|
1578
|
+
changed = true;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
buildLayouts() {
|
|
1585
|
+
this.stateLayout = this.layoutNamedValues(this.mir.state);
|
|
1586
|
+
this.paramLayout = this.layoutNamedValues(this.mir.interface.params);
|
|
1587
|
+
this.inputLayout = this.layoutPorts(this.mir.interface.inputs);
|
|
538
1588
|
this.outputLayout = this.layoutPorts(this.mir.interface.outputs);
|
|
539
1589
|
this.controlOutputLayout = this.layoutControlOutputs();
|
|
540
1590
|
this.eventLayout = this.mir.interface.events.map((event) =>
|
|
@@ -594,6 +1644,18 @@ class MirCompiler {
|
|
|
594
1644
|
return { address, scalar: type.data, size };
|
|
595
1645
|
});
|
|
596
1646
|
});
|
|
1647
|
+
if (this.mir.interface.buffers.length > 0) {
|
|
1648
|
+
const fallbackBytes = Math.max(
|
|
1649
|
+
...this.mir.interface.buffers.map((buffer) =>
|
|
1650
|
+
this.scalarSize(buffer.element)),
|
|
1651
|
+
);
|
|
1652
|
+
this.nextStaticAddress = alignUp(this.nextStaticAddress, 8);
|
|
1653
|
+
this.fallbackBufferReadAddress = this.nextStaticAddress;
|
|
1654
|
+
this.nextStaticAddress += fallbackBytes;
|
|
1655
|
+
this.nextStaticAddress = alignUp(this.nextStaticAddress, 8);
|
|
1656
|
+
this.fallbackBufferWriteAddress = this.nextStaticAddress;
|
|
1657
|
+
this.nextStaticAddress += fallbackBytes;
|
|
1658
|
+
}
|
|
597
1659
|
this.nextStaticAddress = alignUp(this.nextStaticAddress, 16);
|
|
598
1660
|
this.requireWasm32Extent(this.nextStaticAddress, "MIR static storage");
|
|
599
1661
|
this.requireWasm32Extent(
|
|
@@ -616,7 +1678,7 @@ class MirCompiler {
|
|
|
616
1678
|
const parameter = target?.params[index];
|
|
617
1679
|
const type = parameter && this.type(parameter.ty);
|
|
618
1680
|
if (
|
|
619
|
-
|
|
1681
|
+
this.parameterPassingMode(data.function, index) !== "value"
|
|
620
1682
|
&& type?.kind === "scalar"
|
|
621
1683
|
&& argument.kind === "place"
|
|
622
1684
|
&& argument.data.base.kind === "local"
|
|
@@ -845,13 +1907,13 @@ class MirCompiler {
|
|
|
845
1907
|
|
|
846
1908
|
addMirFunction(id, func) {
|
|
847
1909
|
let nextIndex = 0;
|
|
848
|
-
const paramLayouts = func.params.map((param) => {
|
|
1910
|
+
const paramLayouts = func.params.map((param, parameterId) => {
|
|
849
1911
|
const layout = this.functionValueLayout(
|
|
850
1912
|
param.ty,
|
|
851
1913
|
nextIndex,
|
|
852
1914
|
`parameter '${param.name}'`,
|
|
853
1915
|
false,
|
|
854
|
-
|
|
1916
|
+
this.parameterPassingMode(id, parameterId),
|
|
855
1917
|
);
|
|
856
1918
|
nextIndex += layout.components.length;
|
|
857
1919
|
return layout;
|
|
@@ -878,6 +1940,11 @@ class MirCompiler {
|
|
|
878
1940
|
const callResultLocals = this.collectCallResultLocals(func);
|
|
879
1941
|
const sliceScratch = this.collectSliceScratchLocals(func);
|
|
880
1942
|
const processFrameLocals = this.collectProcessFrameLocals(func);
|
|
1943
|
+
const generatedLocalBase =
|
|
1944
|
+
paramScalars.length +
|
|
1945
|
+
flatLocalScalars.length +
|
|
1946
|
+
callResultLocals.length +
|
|
1947
|
+
sliceScratch.count;
|
|
881
1948
|
if (
|
|
882
1949
|
resultScalars.length > 1
|
|
883
1950
|
|| callResultLocals.some((entry) => entry.resultCount > 1)
|
|
@@ -918,10 +1985,21 @@ class MirCompiler {
|
|
|
918
1985
|
),
|
|
919
1986
|
eventId: func.kind?.kind === "event" ? func.kind.data : null,
|
|
920
1987
|
processFrameLocals,
|
|
1988
|
+
generatedLocalBase,
|
|
1989
|
+
generatedLocals: [],
|
|
1990
|
+
entryInitializers: [],
|
|
1991
|
+
bufferDescriptorCache: new Map(),
|
|
1992
|
+
audioChannelPointerCache: new Map(),
|
|
921
1993
|
breakLabels: [],
|
|
922
1994
|
continueLabels: [],
|
|
923
1995
|
};
|
|
924
|
-
const
|
|
1996
|
+
const compiledBody = this.compileBlock(func.body, context);
|
|
1997
|
+
const body = context.entryInitializers.length === 0
|
|
1998
|
+
? compiledBody
|
|
1999
|
+
: this.module.block(null, [
|
|
2000
|
+
...context.entryInitializers,
|
|
2001
|
+
compiledBody,
|
|
2002
|
+
]);
|
|
925
2003
|
const functionRef = this.module.addFunction(
|
|
926
2004
|
this.functionNames[id],
|
|
927
2005
|
binaryen.createType(paramScalars.map((type) => this.wasmType(type))),
|
|
@@ -930,6 +2008,7 @@ class MirCompiler {
|
|
|
930
2008
|
...flatLocalScalars.map((type) => this.wasmType(type)),
|
|
931
2009
|
...callResultLocals.map((entry) => entry.type),
|
|
932
2010
|
...Array.from({ length: sliceScratch.count }, () => binaryen.i32),
|
|
2011
|
+
...context.generatedLocals.map((entry) => this.wasmType(entry.scalar)),
|
|
933
2012
|
],
|
|
934
2013
|
body,
|
|
935
2014
|
);
|
|
@@ -954,6 +2033,9 @@ class MirCompiler {
|
|
|
954
2033
|
);
|
|
955
2034
|
}
|
|
956
2035
|
}
|
|
2036
|
+
for (const local of context.generatedLocals) {
|
|
2037
|
+
binaryen.Function.setLocalName(functionRef, local.index, local.name);
|
|
2038
|
+
}
|
|
957
2039
|
}
|
|
958
2040
|
|
|
959
2041
|
functionValueLayout(
|
|
@@ -981,15 +2063,28 @@ class MirCompiler {
|
|
|
981
2063
|
index,
|
|
982
2064
|
typeId,
|
|
983
2065
|
kind: "slice",
|
|
984
|
-
components: ["i32", "i32", "i32"],
|
|
2066
|
+
components: ["i32", "i32", "i32", "i32"],
|
|
985
2067
|
};
|
|
986
2068
|
}
|
|
987
2069
|
if (type.kind === "buffer") {
|
|
2070
|
+
this.bufferChannelMetadata(type.data.channels, type.data.element);
|
|
988
2071
|
return {
|
|
989
2072
|
index,
|
|
990
2073
|
typeId,
|
|
991
2074
|
kind: "buffer",
|
|
992
|
-
components: ["i32", "i32", "i32", "f32"],
|
|
2075
|
+
components: ["i32", "i32", "i32", "i32", "f32"],
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
if (type.kind === "buffer_span") {
|
|
2079
|
+
this.bufferChannelMetadata(type.data.channels, type.data.element);
|
|
2080
|
+
if (passingMode !== "value") {
|
|
2081
|
+
this.fail(`${description} buffer span must use value passing mode`);
|
|
2082
|
+
}
|
|
2083
|
+
return {
|
|
2084
|
+
index,
|
|
2085
|
+
typeId,
|
|
2086
|
+
kind: "buffer_span",
|
|
2087
|
+
components: ["i32", "i32", "i32", "i32", "i32"],
|
|
993
2088
|
};
|
|
994
2089
|
}
|
|
995
2090
|
if (type.kind === "array") {
|
|
@@ -1018,8 +2113,10 @@ class MirCompiler {
|
|
|
1018
2113
|
}
|
|
1019
2114
|
if (layout.kind === "array") return;
|
|
1020
2115
|
const suffixes = layout.kind === "buffer"
|
|
1021
|
-
? ["
|
|
1022
|
-
:
|
|
2116
|
+
? ["read_address", "write_address", "frames", "channels", "sample_rate"]
|
|
2117
|
+
: layout.kind === "buffer_span"
|
|
2118
|
+
? ["read_table", "write_table", "frames_table", "channels_table", "sample_rates_table"]
|
|
2119
|
+
: ["read_address", "write_address", "length", "stride"];
|
|
1023
2120
|
for (const [offset, suffix] of suffixes.entries()) {
|
|
1024
2121
|
binaryen.Function.setLocalName(
|
|
1025
2122
|
functionRef,
|
|
@@ -1039,7 +2136,7 @@ class MirCompiler {
|
|
|
1039
2136
|
this.requireFunctionId(data.function, "call target");
|
|
1040
2137
|
const target = this.mir.functions[data.function];
|
|
1041
2138
|
const aliasesResult = data.args.some((argument, index) =>
|
|
1042
|
-
|
|
2139
|
+
this.parameterPassingMode(data.function, index) !== "value"
|
|
1043
2140
|
&& argument.kind === "place"
|
|
1044
2141
|
&& argument.data.base.kind === "local"
|
|
1045
2142
|
&& argument.data.projections.length === 0
|
|
@@ -1299,6 +2396,10 @@ class MirCompiler {
|
|
|
1299
2396
|
POINTER_GLOBALS.buffers,
|
|
1300
2397
|
this.module.local.get(7, binaryen.i32),
|
|
1301
2398
|
),
|
|
2399
|
+
this.module.global.set(
|
|
2400
|
+
POINTER_GLOBALS.bufferWrites,
|
|
2401
|
+
this.module.local.get(7, binaryen.i32),
|
|
2402
|
+
),
|
|
1302
2403
|
this.module.global.set(
|
|
1303
2404
|
POINTER_GLOBALS.bufferFrames,
|
|
1304
2405
|
this.module.local.get(8, binaryen.i32),
|
|
@@ -1357,6 +2458,10 @@ class MirCompiler {
|
|
|
1357
2458
|
POINTER_GLOBALS.buffers,
|
|
1358
2459
|
this.module.local.get(3, binaryen.i32),
|
|
1359
2460
|
),
|
|
2461
|
+
this.module.global.set(
|
|
2462
|
+
POINTER_GLOBALS.bufferWrites,
|
|
2463
|
+
this.module.local.get(3, binaryen.i32),
|
|
2464
|
+
),
|
|
1360
2465
|
this.module.global.set(
|
|
1361
2466
|
POINTER_GLOBALS.bufferFrames,
|
|
1362
2467
|
this.module.local.get(4, binaryen.i32),
|
|
@@ -1486,7 +2591,16 @@ class MirCompiler {
|
|
|
1486
2591
|
const args = data.args.flatMap((argument, index) => {
|
|
1487
2592
|
const parameterType = this.type(target.params[index].ty);
|
|
1488
2593
|
if (parameterType.kind === "scalar") {
|
|
1489
|
-
|
|
2594
|
+
const passingMode = this.parameterPassingMode(data.function, index);
|
|
2595
|
+
if (passingMode === "value") {
|
|
2596
|
+
if (target.params[index].mode !== "value") {
|
|
2597
|
+
if (argument.kind !== "place") {
|
|
2598
|
+
this.fail(
|
|
2599
|
+
`promoted scalar reference argument ${index} of '${target.name}' is not a place`,
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
return [this.loadPlace(argument.data, context)];
|
|
2603
|
+
}
|
|
1490
2604
|
if (argument.kind !== "value") {
|
|
1491
2605
|
this.fail(`scalar call argument ${index} of '${target.name}' is not a value`);
|
|
1492
2606
|
}
|
|
@@ -1505,6 +2619,7 @@ class MirCompiler {
|
|
|
1505
2619
|
argument.data.index,
|
|
1506
2620
|
argument.data.bounds,
|
|
1507
2621
|
context,
|
|
2622
|
+
target.params[index].mode === "read_write_reference",
|
|
1508
2623
|
),
|
|
1509
2624
|
];
|
|
1510
2625
|
}
|
|
@@ -1534,22 +2649,32 @@ class MirCompiler {
|
|
|
1534
2649
|
];
|
|
1535
2650
|
}
|
|
1536
2651
|
return [
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
2652
|
+
this.compileSliceWindowAddress(
|
|
2653
|
+
argument.data,
|
|
2654
|
+
parameterType,
|
|
2655
|
+
context,
|
|
2656
|
+
target.params[index].mode === "read_write_reference",
|
|
2657
|
+
),
|
|
1542
2658
|
];
|
|
1543
2659
|
}
|
|
1544
2660
|
if (parameterType.kind === "buffer") {
|
|
1545
2661
|
if (argument.kind === "buffer") {
|
|
1546
|
-
return this.compileInterfaceBufferValue(argument.data);
|
|
2662
|
+
return this.compileInterfaceBufferValue(argument.data, context);
|
|
2663
|
+
}
|
|
2664
|
+
if (argument.kind === "buffer_param") {
|
|
2665
|
+
return this.loadBufferParamValue(argument.data, context);
|
|
1547
2666
|
}
|
|
1548
2667
|
if (argument.kind === "place") {
|
|
1549
2668
|
return this.loadBufferPlace(argument.data, context);
|
|
1550
2669
|
}
|
|
1551
2670
|
this.fail(`buffer call argument ${index} of '${target.name}' is invalid`);
|
|
1552
2671
|
}
|
|
2672
|
+
if (parameterType.kind === "buffer_span") {
|
|
2673
|
+
if (argument.kind !== "buffer_span") {
|
|
2674
|
+
this.fail(`buffer span call argument ${index} of '${target.name}' is invalid`);
|
|
2675
|
+
}
|
|
2676
|
+
return this.compileBufferSpanValue(argument.data, parameterType, context);
|
|
2677
|
+
}
|
|
1553
2678
|
this.fail(
|
|
1554
2679
|
`call argument ${index} of '${target.name}' has unsupported type '${parameterType.kind}'`,
|
|
1555
2680
|
);
|
|
@@ -1565,7 +2690,7 @@ class MirCompiler {
|
|
|
1565
2690
|
const localReferenceSync = data.args.flatMap((argument, index) => {
|
|
1566
2691
|
const parameter = target.params[index];
|
|
1567
2692
|
if (
|
|
1568
|
-
|
|
2693
|
+
this.parameterPassingMode(data.function, index) === "value"
|
|
1569
2694
|
|| argument.kind !== "place"
|
|
1570
2695
|
|| argument.data.base.kind !== "local"
|
|
1571
2696
|
|| argument.data.projections.length !== 0
|
|
@@ -1673,12 +2798,13 @@ class MirCompiler {
|
|
|
1673
2798
|
if (!port) {
|
|
1674
2799
|
this.fail(`output id ${data.output} is out of range`);
|
|
1675
2800
|
}
|
|
1676
|
-
const
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
2801
|
+
const channelPointer = this.audioChannelPointer(
|
|
2802
|
+
POINTER_GLOBALS.outputs,
|
|
2803
|
+
port,
|
|
2804
|
+
data.element,
|
|
2805
|
+
data.bounds,
|
|
2806
|
+
context,
|
|
1680
2807
|
);
|
|
1681
|
-
const channelPointer = this.module.i32.load(0, 4, tableAddress);
|
|
1682
2808
|
const sampleAddress = this.module.i32.add(
|
|
1683
2809
|
channelPointer,
|
|
1684
2810
|
this.module.i32.mul(
|
|
@@ -1735,13 +2861,13 @@ class MirCompiler {
|
|
|
1735
2861
|
}
|
|
1736
2862
|
|
|
1737
2863
|
compileBufferStore(data, context) {
|
|
1738
|
-
const buffer = this.
|
|
2864
|
+
const buffer = this.requireBufferRef(data.buffer);
|
|
1739
2865
|
if (buffer.access !== "read_write") {
|
|
1740
2866
|
this.fail(`buffer '${buffer.name}' is read-only`);
|
|
1741
2867
|
}
|
|
1742
2868
|
return this.storeScalar(
|
|
1743
2869
|
buffer.element,
|
|
1744
|
-
this.compileBufferAddress(data, context),
|
|
2870
|
+
this.compileBufferAddress(data, context, true),
|
|
1745
2871
|
this.compileValue(data.value, context),
|
|
1746
2872
|
);
|
|
1747
2873
|
}
|
|
@@ -1753,7 +2879,7 @@ class MirCompiler {
|
|
|
1753
2879
|
}
|
|
1754
2880
|
return this.storeScalar(
|
|
1755
2881
|
type.data.element,
|
|
1756
|
-
this.compileBufferParamAddress(data, context),
|
|
2882
|
+
this.compileBufferParamAddress(data, context, true),
|
|
1757
2883
|
this.compileValue(data.value, context),
|
|
1758
2884
|
);
|
|
1759
2885
|
}
|
|
@@ -1812,27 +2938,24 @@ class MirCompiler {
|
|
|
1812
2938
|
case "buffer_param_load":
|
|
1813
2939
|
return this.compileBufferParamLoad(data, context);
|
|
1814
2940
|
case "buffer_len":
|
|
1815
|
-
return this.compileBufferLen(data);
|
|
2941
|
+
return this.compileBufferLen(data, context);
|
|
1816
2942
|
case "buffer_param_len":
|
|
1817
2943
|
return this.compileBufferParamLen(data, context);
|
|
1818
2944
|
case "buffer_channels":
|
|
1819
|
-
return this.
|
|
1820
|
-
POINTER_GLOBALS.bufferChannels,
|
|
1821
|
-
data,
|
|
1822
|
-
"i32",
|
|
1823
|
-
);
|
|
2945
|
+
return this.compileBufferChannels(data, context);
|
|
1824
2946
|
case "buffer_param_channels":
|
|
1825
|
-
return this.
|
|
2947
|
+
return this.compileBufferParamChannels(data, context);
|
|
1826
2948
|
case "buffer_sample_rate":
|
|
1827
2949
|
return this.loadBufferTableValue(
|
|
1828
2950
|
POINTER_GLOBALS.bufferSampleRates,
|
|
1829
2951
|
data,
|
|
1830
2952
|
"f32",
|
|
2953
|
+
context,
|
|
1831
2954
|
);
|
|
1832
2955
|
case "buffer_param_sample_rate":
|
|
1833
|
-
return this.loadBufferParamComponent(data,
|
|
2956
|
+
return this.loadBufferParamComponent(data, 4, "f32", context);
|
|
1834
2957
|
case "slice_len":
|
|
1835
|
-
return this.compileSliceValue(data, context)[
|
|
2958
|
+
return this.compileSliceValue(data, context)[2];
|
|
1836
2959
|
case "slice_load":
|
|
1837
2960
|
return this.compileSliceLoad(data, context);
|
|
1838
2961
|
case "make_slice":
|
|
@@ -1862,12 +2985,13 @@ class MirCompiler {
|
|
|
1862
2985
|
if (!port) {
|
|
1863
2986
|
this.fail(`input id ${data.input} is out of range`);
|
|
1864
2987
|
}
|
|
1865
|
-
const
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2988
|
+
const channelPointer = this.audioChannelPointer(
|
|
2989
|
+
POINTER_GLOBALS.inputs,
|
|
2990
|
+
port,
|
|
2991
|
+
data.element,
|
|
2992
|
+
data.bounds,
|
|
2993
|
+
context,
|
|
1869
2994
|
);
|
|
1870
|
-
const channelPointer = this.module.i32.load(0, 4, tableAddress);
|
|
1871
2995
|
const sampleAddress = this.module.i32.add(
|
|
1872
2996
|
channelPointer,
|
|
1873
2997
|
this.module.i32.mul(
|
|
@@ -1911,12 +3035,13 @@ class MirCompiler {
|
|
|
1911
3035
|
if (!port) {
|
|
1912
3036
|
this.fail(`output id ${data.output} is out of range`);
|
|
1913
3037
|
}
|
|
1914
|
-
const
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
3038
|
+
const channelPointer = this.audioChannelPointer(
|
|
3039
|
+
POINTER_GLOBALS.outputs,
|
|
3040
|
+
port,
|
|
3041
|
+
data.element,
|
|
3042
|
+
data.bounds,
|
|
3043
|
+
context,
|
|
1918
3044
|
);
|
|
1919
|
-
const channelPointer = this.module.i32.load(0, 4, tableAddress);
|
|
1920
3045
|
const sampleAddress = this.module.i32.add(
|
|
1921
3046
|
channelPointer,
|
|
1922
3047
|
this.module.i32.mul(
|
|
@@ -1941,6 +3066,62 @@ class MirCompiler {
|
|
|
1941
3066
|
return this.module.i32.add(this.module.i32.const(port.channel), index);
|
|
1942
3067
|
}
|
|
1943
3068
|
|
|
3069
|
+
audioChannelPointer(globalName, port, element, bounds, context) {
|
|
3070
|
+
const staticChannel = this.staticPortChannel(port, element, bounds);
|
|
3071
|
+
if (staticChannel === null) {
|
|
3072
|
+
return this.loadAudioChannelPointer(
|
|
3073
|
+
globalName,
|
|
3074
|
+
this.compilePortChannel(port, element, bounds, context),
|
|
3075
|
+
);
|
|
3076
|
+
}
|
|
3077
|
+
const key = `${globalName}:${staticChannel}`;
|
|
3078
|
+
let local = context.audioChannelPointerCache.get(key);
|
|
3079
|
+
if (local === undefined) {
|
|
3080
|
+
local = this.allocateGeneratedLocal(
|
|
3081
|
+
context,
|
|
3082
|
+
"i32",
|
|
3083
|
+
`audio.${globalName.slice(6)}.${staticChannel}`,
|
|
3084
|
+
);
|
|
3085
|
+
context.audioChannelPointerCache.set(key, local);
|
|
3086
|
+
context.entryInitializers.push(
|
|
3087
|
+
this.module.local.set(
|
|
3088
|
+
local,
|
|
3089
|
+
this.loadAudioChannelPointer(
|
|
3090
|
+
globalName,
|
|
3091
|
+
this.module.i32.const(staticChannel),
|
|
3092
|
+
),
|
|
3093
|
+
),
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
return this.module.local.get(local, binaryen.i32);
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
loadAudioChannelPointer(globalName, channel) {
|
|
3100
|
+
const tableAddress = this.module.i32.add(
|
|
3101
|
+
this.module.global.get(globalName, binaryen.i32),
|
|
3102
|
+
this.module.i32.mul(channel, this.module.i32.const(4)),
|
|
3103
|
+
);
|
|
3104
|
+
return this.module.i32.load(0, 4, tableAddress);
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
staticPortChannel(port, element, bounds) {
|
|
3108
|
+
if (!port.isArray) return port.channel;
|
|
3109
|
+
if (
|
|
3110
|
+
element?.kind !== "constant"
|
|
3111
|
+
|| element.data?.type !== "i32"
|
|
3112
|
+
|| !Number.isInteger(element.data.value)
|
|
3113
|
+
) {
|
|
3114
|
+
return null;
|
|
3115
|
+
}
|
|
3116
|
+
let index = element.data.value;
|
|
3117
|
+
if (bounds === "clamp") {
|
|
3118
|
+
index = Math.min(port.channels - 1, Math.max(0, index));
|
|
3119
|
+
} else if (index < 0 || index >= port.channels) {
|
|
3120
|
+
return null;
|
|
3121
|
+
}
|
|
3122
|
+
return port.channel + index;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
1944
3125
|
compileConstDataLoad(data, context) {
|
|
1945
3126
|
const item = this.constLayout[data.data];
|
|
1946
3127
|
if (!item) {
|
|
@@ -1955,10 +3136,10 @@ class MirCompiler {
|
|
|
1955
3136
|
}
|
|
1956
3137
|
|
|
1957
3138
|
compileBufferLoad(data, context) {
|
|
1958
|
-
const buffer = this.
|
|
3139
|
+
const buffer = this.requireBufferRef(data.buffer);
|
|
1959
3140
|
return this.loadScalar(
|
|
1960
3141
|
buffer.element,
|
|
1961
|
-
this.compileBufferAddress(data, context),
|
|
3142
|
+
this.compileBufferAddress(data, context, false),
|
|
1962
3143
|
);
|
|
1963
3144
|
}
|
|
1964
3145
|
|
|
@@ -1966,163 +3147,798 @@ class MirCompiler {
|
|
|
1966
3147
|
const type = this.bufferParamType(data.parameter, context);
|
|
1967
3148
|
return this.loadScalar(
|
|
1968
3149
|
type.data.element,
|
|
1969
|
-
this.compileBufferParamAddress(data, context),
|
|
3150
|
+
this.compileBufferParamAddress(data, context, false),
|
|
1970
3151
|
);
|
|
1971
3152
|
}
|
|
1972
3153
|
|
|
1973
|
-
compileBufferParamAddress(data, context) {
|
|
3154
|
+
compileBufferParamAddress(data, context, write) {
|
|
1974
3155
|
const type = this.bufferParamType(data.parameter, context);
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
const channels = component(2, "i32");
|
|
1978
|
-
const rawIndex = () => {
|
|
1979
|
-
if (data.channel === null) {
|
|
1980
|
-
return this.compileValue(data.index, context);
|
|
1981
|
-
}
|
|
1982
|
-
return this.module.i32.add(
|
|
1983
|
-
this.module.i32.mul(this.compileValue(data.index, context), channels()),
|
|
1984
|
-
this.compileValue(data.channel, context),
|
|
1985
|
-
);
|
|
1986
|
-
};
|
|
1987
|
-
const index = this.compileDynamicBoundedIndex(
|
|
1988
|
-
rawIndex,
|
|
1989
|
-
() => this.compileBufferParamTotalScalarLen(data.parameter, context),
|
|
1990
|
-
data.bounds,
|
|
3156
|
+
return this.withBufferParamSelector(
|
|
3157
|
+
data.parameter,
|
|
1991
3158
|
context,
|
|
1992
|
-
|
|
3159
|
+
binaryen.i32,
|
|
3160
|
+
(selector, staticSelector, prelude) => {
|
|
3161
|
+
let frames = this.bufferParamComponentFactory(
|
|
3162
|
+
data.parameter,
|
|
3163
|
+
2,
|
|
3164
|
+
"i32",
|
|
3165
|
+
selector,
|
|
3166
|
+
staticSelector,
|
|
3167
|
+
context,
|
|
3168
|
+
);
|
|
3169
|
+
let channels = this.bufferParamChannelsFactory(
|
|
3170
|
+
data.parameter,
|
|
3171
|
+
selector,
|
|
3172
|
+
staticSelector,
|
|
3173
|
+
context,
|
|
3174
|
+
);
|
|
3175
|
+
if (data.parameter?.kind === "array_element" && staticSelector === null) {
|
|
3176
|
+
frames = this.snapshotOperationValue(
|
|
3177
|
+
frames,
|
|
3178
|
+
"i32",
|
|
3179
|
+
"buffer_param.frames",
|
|
3180
|
+
prelude,
|
|
3181
|
+
context,
|
|
3182
|
+
);
|
|
3183
|
+
if (this.bufferChannelMetadata(
|
|
3184
|
+
type.data.channels,
|
|
3185
|
+
type.data.element,
|
|
3186
|
+
).kind === "dynamic") {
|
|
3187
|
+
channels = this.snapshotOperationValue(
|
|
3188
|
+
channels,
|
|
3189
|
+
"i32",
|
|
3190
|
+
"buffer_param.channels",
|
|
3191
|
+
prelude,
|
|
3192
|
+
context,
|
|
3193
|
+
);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
const frame = this.compileDynamicBoundedIndex(
|
|
3197
|
+
() => this.compileValue(data.index, context),
|
|
3198
|
+
frames,
|
|
3199
|
+
data.bounds,
|
|
3200
|
+
context,
|
|
3201
|
+
true,
|
|
3202
|
+
);
|
|
3203
|
+
const index = data.channel === null
|
|
3204
|
+
? frame
|
|
3205
|
+
: this.module.i32.add(
|
|
3206
|
+
this.module.i32.mul(frame, channels()),
|
|
3207
|
+
this.compileDynamicBoundedIndex(
|
|
3208
|
+
() => this.compileValue(data.channel, context),
|
|
3209
|
+
channels,
|
|
3210
|
+
data.bounds,
|
|
3211
|
+
context,
|
|
3212
|
+
true,
|
|
3213
|
+
),
|
|
3214
|
+
);
|
|
3215
|
+
const pointer = this.bufferParamComponentFactory(
|
|
3216
|
+
data.parameter,
|
|
3217
|
+
write ? 1 : 0,
|
|
3218
|
+
"i32",
|
|
3219
|
+
selector,
|
|
3220
|
+
staticSelector,
|
|
3221
|
+
context,
|
|
3222
|
+
);
|
|
3223
|
+
return this.bufferPointerWithOffset(
|
|
3224
|
+
pointer,
|
|
3225
|
+
this.module.i32.mul(
|
|
3226
|
+
index,
|
|
3227
|
+
this.module.i32.const(this.scalarSize(type.data.element)),
|
|
3228
|
+
),
|
|
3229
|
+
write,
|
|
3230
|
+
context,
|
|
3231
|
+
);
|
|
3232
|
+
},
|
|
1993
3233
|
);
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
compileBufferAddress(data, context, write) {
|
|
3237
|
+
const buffer = this.requireBufferRef(data.buffer);
|
|
3238
|
+
return this.withBufferRefIndex(
|
|
3239
|
+
data.buffer,
|
|
3240
|
+
context,
|
|
3241
|
+
binaryen.i32,
|
|
3242
|
+
(descriptorIndex, staticIndex, prelude) => {
|
|
3243
|
+
let frames = this.bufferTableValueFactory(
|
|
3244
|
+
POINTER_GLOBALS.bufferFrames,
|
|
3245
|
+
descriptorIndex,
|
|
3246
|
+
staticIndex,
|
|
3247
|
+
"i32",
|
|
3248
|
+
context,
|
|
3249
|
+
);
|
|
3250
|
+
let channels = this.bufferChannelsFactory(
|
|
3251
|
+
data.buffer,
|
|
3252
|
+
descriptorIndex,
|
|
3253
|
+
staticIndex,
|
|
3254
|
+
context,
|
|
3255
|
+
);
|
|
3256
|
+
if (staticIndex === null) {
|
|
3257
|
+
frames = this.snapshotOperationValue(
|
|
3258
|
+
frames,
|
|
3259
|
+
"i32",
|
|
3260
|
+
"buffer.frames",
|
|
3261
|
+
prelude,
|
|
3262
|
+
context,
|
|
3263
|
+
);
|
|
3264
|
+
if (this.bufferRefChannelMetadata(data.buffer).kind === "dynamic") {
|
|
3265
|
+
channels = this.snapshotOperationValue(
|
|
3266
|
+
channels,
|
|
3267
|
+
"i32",
|
|
3268
|
+
"buffer.channels",
|
|
3269
|
+
prelude,
|
|
3270
|
+
context,
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
const frame = this.compileDynamicBoundedIndex(
|
|
3275
|
+
() => this.compileValue(data.index, context),
|
|
3276
|
+
frames,
|
|
3277
|
+
data.bounds,
|
|
3278
|
+
context,
|
|
3279
|
+
true,
|
|
3280
|
+
);
|
|
3281
|
+
const index = data.channel === null
|
|
3282
|
+
? frame
|
|
3283
|
+
: this.module.i32.add(
|
|
3284
|
+
this.module.i32.mul(frame, channels()),
|
|
3285
|
+
this.compileDynamicBoundedIndex(
|
|
3286
|
+
() => this.compileValue(data.channel, context),
|
|
3287
|
+
channels,
|
|
3288
|
+
data.bounds,
|
|
3289
|
+
context,
|
|
3290
|
+
true,
|
|
3291
|
+
),
|
|
3292
|
+
);
|
|
3293
|
+
const pointer = this.bufferTableValueFactory(
|
|
3294
|
+
write ? POINTER_GLOBALS.bufferWrites : POINTER_GLOBALS.buffers,
|
|
3295
|
+
descriptorIndex,
|
|
3296
|
+
staticIndex,
|
|
3297
|
+
"i32",
|
|
3298
|
+
context,
|
|
3299
|
+
);
|
|
3300
|
+
return this.bufferPointerWithOffset(
|
|
3301
|
+
pointer,
|
|
3302
|
+
this.module.i32.mul(
|
|
3303
|
+
index,
|
|
3304
|
+
this.module.i32.const(this.scalarSize(buffer.element)),
|
|
3305
|
+
),
|
|
3306
|
+
write,
|
|
3307
|
+
context,
|
|
3308
|
+
);
|
|
3309
|
+
},
|
|
3310
|
+
);
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
bufferPointerWithOffset(pointer, byteOffset, write, context) {
|
|
3314
|
+
const local = this.allocateGeneratedLocal(
|
|
3315
|
+
context,
|
|
3316
|
+
"i32",
|
|
3317
|
+
write ? "buffer.write_pointer" : "buffer.read_pointer",
|
|
3318
|
+
);
|
|
3319
|
+
const stablePointer = () => this.module.local.get(local, binaryen.i32);
|
|
3320
|
+
const fallback = write
|
|
3321
|
+
? this.fallbackBufferWriteAddress
|
|
3322
|
+
: this.fallbackBufferReadAddress;
|
|
3323
|
+
return this.module.block(
|
|
3324
|
+
null,
|
|
3325
|
+
[
|
|
3326
|
+
this.module.local.set(local, pointer()),
|
|
3327
|
+
this.module.i32.add(
|
|
3328
|
+
stablePointer(),
|
|
3329
|
+
this.module.select(
|
|
3330
|
+
this.module.i32.eq(
|
|
3331
|
+
stablePointer(),
|
|
3332
|
+
this.module.i32.const(fallback),
|
|
3333
|
+
),
|
|
3334
|
+
this.module.i32.const(0),
|
|
3335
|
+
byteOffset,
|
|
3336
|
+
),
|
|
3337
|
+
),
|
|
3338
|
+
],
|
|
3339
|
+
binaryen.i32,
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
compileBufferLen(bufferRef, context) {
|
|
3344
|
+
this.requireBufferRef(bufferRef);
|
|
3345
|
+
return this.withBufferRefIndex(
|
|
3346
|
+
bufferRef,
|
|
3347
|
+
context,
|
|
3348
|
+
binaryen.i32,
|
|
3349
|
+
(index, staticIndex) => this.bufferTableValueFactory(
|
|
3350
|
+
POINTER_GLOBALS.bufferFrames,
|
|
1997
3351
|
index,
|
|
1998
|
-
|
|
3352
|
+
staticIndex,
|
|
3353
|
+
"i32",
|
|
3354
|
+
context,
|
|
3355
|
+
)(),
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
compileBufferChannels(bufferRef, context) {
|
|
3360
|
+
const channels = this.bufferRefChannelMetadata(bufferRef);
|
|
3361
|
+
if (channels.kind === "mono") return this.module.i32.const(1);
|
|
3362
|
+
if (channels.kind === "static") return this.module.i32.const(channels.count);
|
|
3363
|
+
return this.withBufferRefIndex(
|
|
3364
|
+
bufferRef,
|
|
3365
|
+
context,
|
|
3366
|
+
binaryen.i32,
|
|
3367
|
+
(index, staticIndex) => this.bufferTableValueFactory(
|
|
3368
|
+
POINTER_GLOBALS.bufferChannels,
|
|
3369
|
+
index,
|
|
3370
|
+
staticIndex,
|
|
3371
|
+
"i32",
|
|
3372
|
+
context,
|
|
3373
|
+
)(),
|
|
3374
|
+
);
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
compileBufferParamLen(parameterId, context) {
|
|
3378
|
+
this.bufferParamType(parameterId, context);
|
|
3379
|
+
return this.withBufferParamSelector(
|
|
3380
|
+
parameterId,
|
|
3381
|
+
context,
|
|
3382
|
+
binaryen.i32,
|
|
3383
|
+
(selector, staticSelector) => this.bufferParamComponentFactory(
|
|
3384
|
+
parameterId,
|
|
3385
|
+
2,
|
|
3386
|
+
"i32",
|
|
3387
|
+
selector,
|
|
3388
|
+
staticSelector,
|
|
3389
|
+
context,
|
|
3390
|
+
)(),
|
|
3391
|
+
);
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
compileBufferParamChannels(parameterId, context) {
|
|
3395
|
+
const type = this.bufferParamType(parameterId, context);
|
|
3396
|
+
const channels = this.bufferChannelMetadata(
|
|
3397
|
+
type.data.channels,
|
|
3398
|
+
type.data.element,
|
|
3399
|
+
);
|
|
3400
|
+
if (channels.kind === "mono") return this.module.i32.const(1);
|
|
3401
|
+
if (channels.kind === "static") return this.module.i32.const(channels.count);
|
|
3402
|
+
return this.withBufferParamSelector(
|
|
3403
|
+
parameterId,
|
|
3404
|
+
context,
|
|
3405
|
+
binaryen.i32,
|
|
3406
|
+
(selector, staticSelector) => this.bufferParamComponentFactory(
|
|
3407
|
+
parameterId,
|
|
3408
|
+
3,
|
|
3409
|
+
"i32",
|
|
3410
|
+
selector,
|
|
3411
|
+
staticSelector,
|
|
3412
|
+
context,
|
|
3413
|
+
)(),
|
|
3414
|
+
);
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
bufferParamType(parameterId, context) {
|
|
3418
|
+
const parameter = context.function.params[this.bufferParamIds(parameterId, context)[0]];
|
|
3419
|
+
const type = parameter && this.type(parameter.ty);
|
|
3420
|
+
const expectedKind = parameterId?.kind === "array_element"
|
|
3421
|
+
? "buffer_span"
|
|
3422
|
+
: "buffer";
|
|
3423
|
+
if (!type || type.kind !== expectedKind) {
|
|
3424
|
+
this.fail(`parameter id ${parameterId} is not a buffer`);
|
|
3425
|
+
}
|
|
3426
|
+
return type;
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
bufferParamIds(parameterRef, context) {
|
|
3430
|
+
if (Number.isInteger(parameterRef)) return [parameterRef];
|
|
3431
|
+
if (
|
|
3432
|
+
parameterRef?.kind === "direct"
|
|
3433
|
+
&& Number.isInteger(parameterRef.data)
|
|
3434
|
+
) {
|
|
3435
|
+
return [parameterRef.data];
|
|
3436
|
+
}
|
|
3437
|
+
if (
|
|
3438
|
+
parameterRef?.kind === "array_element"
|
|
3439
|
+
&& Number.isInteger(parameterRef.data?.span)
|
|
3440
|
+
&& parameterRef.data.span >= 0
|
|
3441
|
+
&& parameterRef.data.span < context.function.params.length
|
|
3442
|
+
) {
|
|
3443
|
+
return [parameterRef.data.span];
|
|
3444
|
+
}
|
|
3445
|
+
this.fail("invalid buffer parameter reference");
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
bufferParamLayout(parameterId, context) {
|
|
3449
|
+
const layout = context.paramLayouts[parameterId];
|
|
3450
|
+
if (!layout || !["buffer", "buffer_span"].includes(layout.kind)) {
|
|
3451
|
+
this.fail(`parameter id ${parameterId} has no buffer descriptor`);
|
|
3452
|
+
}
|
|
3453
|
+
return layout;
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
staticBufferParamSelector(parameterRef, context) {
|
|
3457
|
+
if (parameterRef?.kind !== "array_element") return null;
|
|
3458
|
+
const reference = parameterRef.data;
|
|
3459
|
+
const type = this.bufferParamType(parameterRef, context);
|
|
3460
|
+
const selector = reference.selector;
|
|
3461
|
+
if (
|
|
3462
|
+
selector?.kind !== "constant"
|
|
3463
|
+
|| selector.data?.type !== "i32"
|
|
3464
|
+
|| !Number.isInteger(selector.data.value)
|
|
3465
|
+
) {
|
|
3466
|
+
return null;
|
|
3467
|
+
}
|
|
3468
|
+
let index = selector.data.value;
|
|
3469
|
+
if (reference.bounds === "clamp") {
|
|
3470
|
+
index = Math.min(type.data.len - 1, Math.max(0, index));
|
|
3471
|
+
} else if (index < 0 || index >= type.data.len) {
|
|
3472
|
+
return null;
|
|
3473
|
+
}
|
|
3474
|
+
return index;
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
compileBufferParamSelector(parameterRef, context) {
|
|
3478
|
+
const reference = parameterRef.data;
|
|
3479
|
+
const type = this.bufferParamType(parameterRef, context);
|
|
3480
|
+
return this.compileDynamicBoundedIndex(
|
|
3481
|
+
() => this.compileValue(reference.selector, context),
|
|
3482
|
+
() => this.module.i32.const(type.data.len),
|
|
3483
|
+
reference.bounds,
|
|
3484
|
+
context,
|
|
3485
|
+
true,
|
|
3486
|
+
);
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
withBufferParamSelector(parameterRef, context, resultType, build) {
|
|
3490
|
+
if (parameterRef?.kind !== "array_element") {
|
|
3491
|
+
return build(null, null, []);
|
|
3492
|
+
}
|
|
3493
|
+
const staticSelector = this.staticBufferParamSelector(parameterRef, context);
|
|
3494
|
+
if (staticSelector !== null) {
|
|
3495
|
+
return build(
|
|
3496
|
+
() => this.module.i32.const(staticSelector),
|
|
3497
|
+
staticSelector,
|
|
3498
|
+
[],
|
|
3499
|
+
);
|
|
3500
|
+
}
|
|
3501
|
+
const selectorLocal = this.allocateGeneratedLocal(
|
|
3502
|
+
context,
|
|
3503
|
+
"i32",
|
|
3504
|
+
"buffer_param.selector",
|
|
3505
|
+
);
|
|
3506
|
+
const prelude = [
|
|
3507
|
+
this.module.local.set(
|
|
3508
|
+
selectorLocal,
|
|
3509
|
+
this.compileBufferParamSelector(parameterRef, context),
|
|
1999
3510
|
),
|
|
3511
|
+
];
|
|
3512
|
+
const result = build(
|
|
3513
|
+
() => this.module.local.get(selectorLocal, binaryen.i32),
|
|
3514
|
+
null,
|
|
3515
|
+
prelude,
|
|
2000
3516
|
);
|
|
3517
|
+
return this.module.block(null, [...prelude, result], resultType);
|
|
2001
3518
|
}
|
|
2002
3519
|
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
3520
|
+
bufferParamComponentFactory(
|
|
3521
|
+
parameterRef,
|
|
3522
|
+
offset,
|
|
3523
|
+
scalar,
|
|
3524
|
+
selector,
|
|
3525
|
+
staticSelector,
|
|
3526
|
+
context,
|
|
3527
|
+
) {
|
|
3528
|
+
const parameterId = this.bufferParamIds(parameterRef, context)[0];
|
|
3529
|
+
const layout = this.bufferParamLayout(parameterId, context);
|
|
3530
|
+
if (parameterRef?.kind !== "array_element") {
|
|
3531
|
+
return () =>
|
|
3532
|
+
this.module.local.get(layout.index + offset, this.wasmType(scalar));
|
|
3533
|
+
}
|
|
3534
|
+
const rawLoad = (index) => {
|
|
3535
|
+
const table = this.module.local.get(layout.index + offset, binaryen.i32);
|
|
3536
|
+
const address = this.module.i32.add(
|
|
3537
|
+
table,
|
|
2010
3538
|
this.module.i32.mul(
|
|
2011
|
-
|
|
2012
|
-
this.
|
|
2013
|
-
POINTER_GLOBALS.bufferChannels,
|
|
2014
|
-
data.buffer,
|
|
2015
|
-
"i32",
|
|
2016
|
-
),
|
|
3539
|
+
index,
|
|
3540
|
+
this.module.i32.const(this.scalarSize(scalar)),
|
|
2017
3541
|
),
|
|
2018
|
-
this.compileValue(data.channel, context),
|
|
2019
3542
|
);
|
|
3543
|
+
return this.loadScalar(scalar, address);
|
|
2020
3544
|
};
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
3545
|
+
const load = (index) => {
|
|
3546
|
+
if (scalar !== "i32" || (offset !== 0 && offset !== 1)) {
|
|
3547
|
+
return rawLoad(index);
|
|
3548
|
+
}
|
|
3549
|
+
return this.resolveBufferPointer(
|
|
3550
|
+
() => rawLoad(index),
|
|
3551
|
+
offset === 1,
|
|
3552
|
+
context,
|
|
3553
|
+
);
|
|
3554
|
+
};
|
|
3555
|
+
if (staticSelector === null) return () => load(selector());
|
|
3556
|
+
|
|
3557
|
+
const key = `buffer_param:${parameterId}:${staticSelector}:${offset}:${scalar}`;
|
|
3558
|
+
let local = context.bufferDescriptorCache.get(key);
|
|
3559
|
+
if (local === undefined) {
|
|
3560
|
+
local = this.allocateGeneratedLocal(
|
|
3561
|
+
context,
|
|
3562
|
+
scalar,
|
|
3563
|
+
`buffer_param.component${offset}.${parameterId}.${staticSelector}`,
|
|
3564
|
+
);
|
|
3565
|
+
context.bufferDescriptorCache.set(key, local);
|
|
3566
|
+
context.entryInitializers.push(
|
|
3567
|
+
this.module.local.set(
|
|
3568
|
+
local,
|
|
3569
|
+
load(this.module.i32.const(staticSelector)),
|
|
3570
|
+
),
|
|
3571
|
+
);
|
|
3572
|
+
}
|
|
3573
|
+
return () => this.module.local.get(local, this.wasmType(scalar));
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
bufferParamChannelsFactory(
|
|
3577
|
+
parameterRef,
|
|
3578
|
+
selector,
|
|
3579
|
+
staticSelector,
|
|
3580
|
+
context,
|
|
3581
|
+
) {
|
|
3582
|
+
const type = this.bufferParamType(parameterRef, context);
|
|
3583
|
+
const channels = this.bufferChannelMetadata(
|
|
3584
|
+
type.data.channels,
|
|
3585
|
+
type.data.element,
|
|
2027
3586
|
);
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
3587
|
+
if (channels.kind === "mono") return () => this.module.i32.const(1);
|
|
3588
|
+
if (channels.kind === "static") {
|
|
3589
|
+
return () => this.module.i32.const(channels.count);
|
|
3590
|
+
}
|
|
3591
|
+
return this.bufferParamComponentFactory(
|
|
3592
|
+
parameterRef,
|
|
3593
|
+
3,
|
|
2031
3594
|
"i32",
|
|
3595
|
+
selector,
|
|
3596
|
+
staticSelector,
|
|
3597
|
+
context,
|
|
2032
3598
|
);
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
loadBufferParamComponent(parameterId, offset, scalar, context) {
|
|
3602
|
+
return this.withBufferParamSelector(
|
|
3603
|
+
parameterId,
|
|
3604
|
+
context,
|
|
3605
|
+
this.wasmType(scalar),
|
|
3606
|
+
(selector, staticSelector) => this.bufferParamComponentFactory(
|
|
3607
|
+
parameterId,
|
|
3608
|
+
offset,
|
|
3609
|
+
scalar,
|
|
3610
|
+
selector,
|
|
3611
|
+
staticSelector,
|
|
3612
|
+
context,
|
|
3613
|
+
)(),
|
|
3614
|
+
);
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
loadBufferParamValue(parameterId, context) {
|
|
3618
|
+
const components = ["i32", "i32", "i32", "i32", "f32"];
|
|
3619
|
+
if (parameterId?.kind !== "array_element") {
|
|
3620
|
+
return components.map((scalar, offset) =>
|
|
3621
|
+
this.loadBufferParamComponent(parameterId, offset, scalar, context),
|
|
3622
|
+
);
|
|
3623
|
+
}
|
|
3624
|
+
const staticSelector = this.staticBufferParamSelector(parameterId, context);
|
|
3625
|
+
let selector;
|
|
3626
|
+
let initializeSelector = null;
|
|
3627
|
+
if (staticSelector === null) {
|
|
3628
|
+
const selectorLocal = this.allocateGeneratedLocal(
|
|
3629
|
+
context,
|
|
3630
|
+
"i32",
|
|
3631
|
+
"buffer_param.selector",
|
|
3632
|
+
);
|
|
3633
|
+
selector = () => this.module.local.get(selectorLocal, binaryen.i32);
|
|
3634
|
+
initializeSelector = this.module.local.set(
|
|
3635
|
+
selectorLocal,
|
|
3636
|
+
this.compileBufferParamSelector(parameterId, context),
|
|
3637
|
+
);
|
|
3638
|
+
} else {
|
|
3639
|
+
selector = () => this.module.i32.const(staticSelector);
|
|
3640
|
+
}
|
|
3641
|
+
const values = components.map((scalar, offset) =>
|
|
3642
|
+
this.bufferParamComponentFactory(
|
|
3643
|
+
parameterId,
|
|
3644
|
+
offset,
|
|
3645
|
+
scalar,
|
|
3646
|
+
selector,
|
|
3647
|
+
staticSelector,
|
|
3648
|
+
context,
|
|
3649
|
+
)(),
|
|
3650
|
+
);
|
|
3651
|
+
if (initializeSelector !== null) {
|
|
3652
|
+
values[0] = this.module.block(
|
|
3653
|
+
null,
|
|
3654
|
+
[initializeSelector, values[0]],
|
|
3655
|
+
binaryen.i32,
|
|
3656
|
+
);
|
|
3657
|
+
}
|
|
3658
|
+
return values;
|
|
3659
|
+
}
|
|
3660
|
+
|
|
3661
|
+
loadBufferPlace(place, context) {
|
|
3662
|
+
if (place.base.kind !== "parameter" || place.projections.length !== 0) {
|
|
3663
|
+
this.fail("buffer call arguments must be unprojected buffer parameters");
|
|
3664
|
+
}
|
|
3665
|
+
const layout = this.bufferParamLayout(place.base.data, context);
|
|
3666
|
+
return layout.components.map((scalar, offset) =>
|
|
3667
|
+
this.module.local.get(layout.index + offset, this.wasmType(scalar)),
|
|
3668
|
+
);
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
compileInterfaceBufferValue(bufferRef, context) {
|
|
3672
|
+
this.requireBufferRef(bufferRef);
|
|
3673
|
+
const staticIndex = this.staticBufferRefIndex(bufferRef);
|
|
3674
|
+
let descriptorIndex;
|
|
3675
|
+
let initializeIndex = null;
|
|
3676
|
+
if (staticIndex === null) {
|
|
3677
|
+
const indexLocal = this.allocateGeneratedLocal(
|
|
3678
|
+
context,
|
|
3679
|
+
"i32",
|
|
3680
|
+
"buffer.descriptor_index",
|
|
3681
|
+
);
|
|
3682
|
+
descriptorIndex = () =>
|
|
3683
|
+
this.module.local.get(indexLocal, binaryen.i32);
|
|
3684
|
+
initializeIndex = this.module.local.set(
|
|
3685
|
+
indexLocal,
|
|
3686
|
+
this.compileBufferRefIndex(bufferRef, context),
|
|
3687
|
+
);
|
|
3688
|
+
} else {
|
|
3689
|
+
descriptorIndex = () => this.module.i32.const(staticIndex);
|
|
3690
|
+
}
|
|
3691
|
+
const component = (globalName, scalar) => this.bufferTableValueFactory(
|
|
3692
|
+
globalName,
|
|
3693
|
+
descriptorIndex,
|
|
3694
|
+
staticIndex,
|
|
3695
|
+
scalar,
|
|
3696
|
+
context,
|
|
3697
|
+
)();
|
|
3698
|
+
const channels = this.bufferRefChannelMetadata(bufferRef);
|
|
3699
|
+
const values = [
|
|
3700
|
+
component(POINTER_GLOBALS.buffers, "i32"),
|
|
3701
|
+
component(POINTER_GLOBALS.bufferWrites, "i32"),
|
|
3702
|
+
component(POINTER_GLOBALS.bufferFrames, "i32"),
|
|
3703
|
+
channels.kind === "mono"
|
|
3704
|
+
? this.module.i32.const(1)
|
|
3705
|
+
: channels.kind === "static"
|
|
3706
|
+
? this.module.i32.const(channels.count)
|
|
3707
|
+
: component(POINTER_GLOBALS.bufferChannels, "i32"),
|
|
3708
|
+
component(POINTER_GLOBALS.bufferSampleRates, "f32"),
|
|
3709
|
+
];
|
|
3710
|
+
if (initializeIndex !== null) {
|
|
3711
|
+
values[0] = this.module.block(
|
|
3712
|
+
null,
|
|
3713
|
+
[initializeIndex, values[0]],
|
|
3714
|
+
binaryen.i32,
|
|
3715
|
+
);
|
|
3716
|
+
}
|
|
3717
|
+
return values;
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3720
|
+
compileBufferSpanValue(spanRef, expectedType, context) {
|
|
3721
|
+
const data = spanRef?.data;
|
|
3722
|
+
if (!data || data.len !== expectedType.data.len) {
|
|
3723
|
+
this.fail("buffer span argument length does not match parameter type");
|
|
3724
|
+
}
|
|
3725
|
+
const tableGlobals = [
|
|
3726
|
+
POINTER_GLOBALS.buffers,
|
|
3727
|
+
POINTER_GLOBALS.bufferWrites,
|
|
3728
|
+
POINTER_GLOBALS.bufferFrames,
|
|
3729
|
+
POINTER_GLOBALS.bufferChannels,
|
|
3730
|
+
POINTER_GLOBALS.bufferSampleRates,
|
|
3731
|
+
];
|
|
3732
|
+
const tableScalars = ["i32", "i32", "i32", "i32", "f32"];
|
|
3733
|
+
let tables;
|
|
3734
|
+
let start;
|
|
3735
|
+
if (spanRef.kind === "interface") {
|
|
3736
|
+
if (
|
|
3737
|
+
!Number.isInteger(data.first)
|
|
3738
|
+
|| data.first < 0
|
|
3739
|
+
|| data.first + data.len > this.mir.interface.buffers.length
|
|
3740
|
+
) {
|
|
3741
|
+
this.fail("interface buffer span is out of range");
|
|
3742
|
+
}
|
|
3743
|
+
tables = tableGlobals.map((name) => this.module.global.get(name, binaryen.i32));
|
|
3744
|
+
start = data.first;
|
|
3745
|
+
} else if (spanRef.kind === "parameter") {
|
|
3746
|
+
const parameter = context.function.params[data.span];
|
|
3747
|
+
const sourceType = parameter && this.type(parameter.ty);
|
|
3748
|
+
const layout = context.paramLayouts[data.span];
|
|
3749
|
+
if (
|
|
3750
|
+
!sourceType
|
|
3751
|
+
|| sourceType.kind !== "buffer_span"
|
|
3752
|
+
|| !layout
|
|
3753
|
+
|| layout.kind !== "buffer_span"
|
|
3754
|
+
|| !Number.isInteger(data.start)
|
|
3755
|
+
|| data.start < 0
|
|
3756
|
+
|| data.start + data.len > sourceType.data.len
|
|
3757
|
+
) {
|
|
3758
|
+
this.fail("buffer span parameter window is out of range");
|
|
3759
|
+
}
|
|
3760
|
+
tables = layout.components.map((_, offset) =>
|
|
3761
|
+
this.module.local.get(layout.index + offset, binaryen.i32));
|
|
3762
|
+
start = data.start;
|
|
3763
|
+
} else {
|
|
3764
|
+
this.fail("invalid buffer span reference");
|
|
3765
|
+
}
|
|
3766
|
+
return tables.map((table, offset) => this.module.i32.add(
|
|
3767
|
+
table,
|
|
3768
|
+
this.module.i32.const(start * this.scalarSize(tableScalars[offset])),
|
|
3769
|
+
));
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
loadBufferTableValue(globalName, bufferRef, scalar, context) {
|
|
3773
|
+
this.requireBufferRef(bufferRef);
|
|
3774
|
+
return this.withBufferRefIndex(
|
|
3775
|
+
bufferRef,
|
|
3776
|
+
context,
|
|
3777
|
+
this.wasmType(scalar),
|
|
3778
|
+
(index, staticIndex) => this.bufferTableValueFactory(
|
|
3779
|
+
globalName,
|
|
2036
3780
|
index,
|
|
2037
|
-
|
|
2038
|
-
|
|
3781
|
+
staticIndex,
|
|
3782
|
+
scalar,
|
|
3783
|
+
context,
|
|
3784
|
+
)(),
|
|
2039
3785
|
);
|
|
2040
3786
|
}
|
|
2041
3787
|
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
3788
|
+
bufferTableValueFactory(
|
|
3789
|
+
globalName,
|
|
3790
|
+
descriptorIndex,
|
|
3791
|
+
staticIndex,
|
|
3792
|
+
scalar,
|
|
3793
|
+
context,
|
|
3794
|
+
) {
|
|
3795
|
+
const load = () => {
|
|
3796
|
+
const raw = () => this.loadBufferTableValueAt(
|
|
3797
|
+
globalName,
|
|
3798
|
+
descriptorIndex(),
|
|
3799
|
+
scalar,
|
|
3800
|
+
);
|
|
3801
|
+
if (
|
|
3802
|
+
scalar === "i32"
|
|
3803
|
+
&& (globalName === POINTER_GLOBALS.buffers
|
|
3804
|
+
|| globalName === POINTER_GLOBALS.bufferWrites)
|
|
3805
|
+
) {
|
|
3806
|
+
return this.resolveBufferPointer(
|
|
3807
|
+
raw,
|
|
3808
|
+
globalName === POINTER_GLOBALS.bufferWrites,
|
|
3809
|
+
context,
|
|
3810
|
+
);
|
|
3811
|
+
}
|
|
3812
|
+
return raw();
|
|
3813
|
+
};
|
|
3814
|
+
if (staticIndex === null) {
|
|
3815
|
+
return load;
|
|
3816
|
+
}
|
|
3817
|
+
const key = `${globalName}:${staticIndex}:${scalar}`;
|
|
3818
|
+
let local = context.bufferDescriptorCache.get(key);
|
|
3819
|
+
if (local === undefined) {
|
|
3820
|
+
local = this.allocateGeneratedLocal(
|
|
3821
|
+
context,
|
|
3822
|
+
scalar,
|
|
3823
|
+
`buffer.${globalName.slice(6)}.${staticIndex}`,
|
|
3824
|
+
);
|
|
3825
|
+
context.bufferDescriptorCache.set(key, local);
|
|
3826
|
+
context.entryInitializers.push(
|
|
3827
|
+
this.module.local.set(
|
|
3828
|
+
local,
|
|
3829
|
+
load(),
|
|
3830
|
+
),
|
|
3831
|
+
);
|
|
3832
|
+
}
|
|
3833
|
+
return () => this.module.local.get(local, this.wasmType(scalar));
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
bufferChannelsFactory(
|
|
3837
|
+
bufferRef,
|
|
3838
|
+
descriptorIndex,
|
|
3839
|
+
staticIndex,
|
|
3840
|
+
context,
|
|
3841
|
+
) {
|
|
3842
|
+
const channels = this.bufferRefChannelMetadata(bufferRef);
|
|
3843
|
+
if (channels.kind === "mono") {
|
|
3844
|
+
return () => this.module.i32.const(1);
|
|
3845
|
+
}
|
|
3846
|
+
if (channels.kind === "static") {
|
|
3847
|
+
return () => this.module.i32.const(channels.count);
|
|
3848
|
+
}
|
|
3849
|
+
return this.bufferTableValueFactory(
|
|
3850
|
+
POINTER_GLOBALS.bufferChannels,
|
|
3851
|
+
descriptorIndex,
|
|
3852
|
+
staticIndex,
|
|
2047
3853
|
"i32",
|
|
3854
|
+
context,
|
|
2048
3855
|
);
|
|
2049
3856
|
}
|
|
2050
3857
|
|
|
2051
|
-
|
|
2052
|
-
this.
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
this.
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
3858
|
+
loadBufferTableValueAt(globalName, descriptorIndex, scalar) {
|
|
3859
|
+
const size = this.scalarSize(scalar);
|
|
3860
|
+
const load = () => this.loadScalar(
|
|
3861
|
+
scalar,
|
|
3862
|
+
this.module.i32.add(
|
|
3863
|
+
this.module.global.get(globalName, binaryen.i32),
|
|
3864
|
+
this.module.i32.mul(
|
|
3865
|
+
descriptorIndex,
|
|
3866
|
+
this.module.i32.const(size),
|
|
3867
|
+
),
|
|
2059
3868
|
),
|
|
2060
3869
|
);
|
|
3870
|
+
return load();
|
|
2061
3871
|
}
|
|
2062
3872
|
|
|
2063
|
-
|
|
2064
|
-
this.
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
this.
|
|
2070
|
-
return this.module.
|
|
2071
|
-
|
|
2072
|
-
|
|
3873
|
+
resolveBufferPointer(load, write, context) {
|
|
3874
|
+
const local = this.allocateGeneratedLocal(
|
|
3875
|
+
context,
|
|
3876
|
+
"i32",
|
|
3877
|
+
write ? "buffer.write_or_discard" : "buffer.read_or_zero",
|
|
3878
|
+
);
|
|
3879
|
+
const pointer = () => this.module.local.get(local, binaryen.i32);
|
|
3880
|
+
return this.module.block(
|
|
3881
|
+
null,
|
|
3882
|
+
[
|
|
3883
|
+
this.module.local.set(local, load()),
|
|
3884
|
+
this.module.select(
|
|
3885
|
+
this.module.i32.ne(pointer(), this.module.i32.const(0)),
|
|
3886
|
+
pointer(),
|
|
3887
|
+
this.module.i32.const(
|
|
3888
|
+
write
|
|
3889
|
+
? this.fallbackBufferWriteAddress
|
|
3890
|
+
: this.fallbackBufferReadAddress,
|
|
3891
|
+
),
|
|
3892
|
+
),
|
|
3893
|
+
],
|
|
3894
|
+
binaryen.i32,
|
|
2073
3895
|
);
|
|
2074
3896
|
}
|
|
2075
3897
|
|
|
2076
|
-
|
|
2077
|
-
const
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
bufferParamLayout(parameterId, context) {
|
|
2086
|
-
const layout = context.paramLayouts[parameterId];
|
|
2087
|
-
if (!layout || layout.kind !== "buffer") {
|
|
2088
|
-
this.fail(`parameter id ${parameterId} has no buffer descriptor`);
|
|
2089
|
-
}
|
|
2090
|
-
return layout;
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
loadBufferParamComponent(parameterId, offset, scalar, context) {
|
|
2094
|
-
const layout = this.bufferParamLayout(parameterId, context);
|
|
2095
|
-
return this.module.local.get(layout.index + offset, this.wasmType(scalar));
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
loadBufferPlace(place, context) {
|
|
2099
|
-
if (place.base.kind !== "parameter" || place.projections.length !== 0) {
|
|
2100
|
-
this.fail("buffer call arguments must be unprojected buffer parameters");
|
|
3898
|
+
withBufferRefIndex(bufferRef, context, resultType, build) {
|
|
3899
|
+
const staticIndex = this.staticBufferRefIndex(bufferRef);
|
|
3900
|
+
if (staticIndex !== null) {
|
|
3901
|
+
return build(
|
|
3902
|
+
() => this.module.i32.const(staticIndex),
|
|
3903
|
+
staticIndex,
|
|
3904
|
+
[],
|
|
3905
|
+
);
|
|
2101
3906
|
}
|
|
2102
|
-
const
|
|
2103
|
-
|
|
2104
|
-
|
|
3907
|
+
const indexLocal = this.allocateGeneratedLocal(
|
|
3908
|
+
context,
|
|
3909
|
+
"i32",
|
|
3910
|
+
"buffer.descriptor_index",
|
|
2105
3911
|
);
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
this.loadBufferTableValue(POINTER_GLOBALS.buffers, bufferId, "i32"),
|
|
2112
|
-
this.loadBufferTableValue(POINTER_GLOBALS.bufferFrames, bufferId, "i32"),
|
|
2113
|
-
this.loadBufferTableValue(POINTER_GLOBALS.bufferChannels, bufferId, "i32"),
|
|
2114
|
-
this.loadBufferTableValue(POINTER_GLOBALS.bufferSampleRates, bufferId, "f32"),
|
|
3912
|
+
const prelude = [
|
|
3913
|
+
this.module.local.set(
|
|
3914
|
+
indexLocal,
|
|
3915
|
+
this.compileBufferRefIndex(bufferRef, context),
|
|
3916
|
+
),
|
|
2115
3917
|
];
|
|
3918
|
+
const result = build(
|
|
3919
|
+
() => this.module.local.get(indexLocal, binaryen.i32),
|
|
3920
|
+
null,
|
|
3921
|
+
prelude,
|
|
3922
|
+
);
|
|
3923
|
+
return this.module.block(null, [...prelude, result], resultType);
|
|
2116
3924
|
}
|
|
2117
3925
|
|
|
2118
|
-
|
|
2119
|
-
this.
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
this.module.global.get(globalName, binaryen.i32),
|
|
2123
|
-
this.module.i32.const(bufferId * size),
|
|
3926
|
+
snapshotOperationValue(factory, scalar, name, prelude, context) {
|
|
3927
|
+
const local = this.allocateGeneratedLocal(context, scalar, name);
|
|
3928
|
+
prelude.push(
|
|
3929
|
+
this.module.local.set(local, factory()),
|
|
2124
3930
|
);
|
|
2125
|
-
return this.
|
|
3931
|
+
return () => this.module.local.get(local, this.wasmType(scalar));
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
allocateGeneratedLocal(context, scalar, name) {
|
|
3935
|
+
const index = context.generatedLocalBase + context.generatedLocals.length;
|
|
3936
|
+
context.generatedLocals.push({
|
|
3937
|
+
index,
|
|
3938
|
+
scalar,
|
|
3939
|
+
name: `${name}.generated${context.generatedLocals.length}`,
|
|
3940
|
+
});
|
|
3941
|
+
return index;
|
|
2126
3942
|
}
|
|
2127
3943
|
|
|
2128
3944
|
compileSliceValue(value, context) {
|
|
@@ -2156,8 +3972,11 @@ class MirCompiler {
|
|
|
2156
3972
|
}
|
|
2157
3973
|
const header = () =>
|
|
2158
3974
|
this.compileEventParamAddress(context.eventId, place.base.data);
|
|
3975
|
+
const address = () =>
|
|
3976
|
+
this.module.i32.add(header(), this.module.i32.const(4));
|
|
2159
3977
|
return [
|
|
2160
|
-
|
|
3978
|
+
address(),
|
|
3979
|
+
address(),
|
|
2161
3980
|
this.module.i32.load(0, 4, header()),
|
|
2162
3981
|
this.module.i32.const(this.scalarSize(type.data.element)),
|
|
2163
3982
|
];
|
|
@@ -2217,7 +4036,7 @@ class MirCompiler {
|
|
|
2217
4036
|
this.fail("slice assignment destination must be an unprojected local");
|
|
2218
4037
|
}
|
|
2219
4038
|
const layout = context.localLayouts[place.base.data];
|
|
2220
|
-
if (!layout || layout.kind !== "slice" || components.length !==
|
|
4039
|
+
if (!layout || layout.kind !== "slice" || components.length !== 4) {
|
|
2221
4040
|
this.fail(`local id ${place.base.data} is not a valid slice destination`);
|
|
2222
4041
|
}
|
|
2223
4042
|
return this.module.block(
|
|
@@ -2229,25 +4048,50 @@ class MirCompiler {
|
|
|
2229
4048
|
}
|
|
2230
4049
|
|
|
2231
4050
|
compileMakeSlice(data, context) {
|
|
2232
|
-
const
|
|
4051
|
+
const sourceValues = this.compileSliceSource(data.source, context);
|
|
4052
|
+
const sourceLocals = sourceValues.map((_, offset) =>
|
|
4053
|
+
this.allocateGeneratedLocal(
|
|
4054
|
+
context,
|
|
4055
|
+
"i32",
|
|
4056
|
+
`slice.source_component${offset}`,
|
|
4057
|
+
),
|
|
4058
|
+
);
|
|
4059
|
+
const initializeSource = sourceValues.map((value, offset) =>
|
|
4060
|
+
this.module.local.set(sourceLocals[offset], value),
|
|
4061
|
+
);
|
|
4062
|
+
const source = (offset) => () =>
|
|
4063
|
+
this.module.local.get(sourceLocals[offset], binaryen.i32);
|
|
2233
4064
|
const range = this.compileSliceRange(
|
|
2234
4065
|
() => this.compileValue(data.start, context),
|
|
2235
4066
|
() => this.compileValue(data.len, context),
|
|
2236
|
-
|
|
4067
|
+
source(2),
|
|
2237
4068
|
data.bounds,
|
|
2238
4069
|
context,
|
|
2239
4070
|
);
|
|
2240
|
-
|
|
4071
|
+
const result = [
|
|
2241
4072
|
this.module.i32.add(
|
|
2242
|
-
source()
|
|
4073
|
+
source(0)(),
|
|
2243
4074
|
this.module.i32.mul(
|
|
2244
4075
|
range.start(),
|
|
2245
|
-
source()
|
|
4076
|
+
source(3)(),
|
|
4077
|
+
),
|
|
4078
|
+
),
|
|
4079
|
+
this.module.i32.add(
|
|
4080
|
+
source(1)(),
|
|
4081
|
+
this.module.i32.mul(
|
|
4082
|
+
range.start(),
|
|
4083
|
+
source(3)(),
|
|
2246
4084
|
),
|
|
2247
4085
|
),
|
|
2248
4086
|
range.len(),
|
|
2249
|
-
source()
|
|
4087
|
+
source(3)(),
|
|
2250
4088
|
];
|
|
4089
|
+
result[0] = this.module.block(
|
|
4090
|
+
null,
|
|
4091
|
+
[...initializeSource, result[0]],
|
|
4092
|
+
binaryen.i32,
|
|
4093
|
+
);
|
|
4094
|
+
return result;
|
|
2251
4095
|
}
|
|
2252
4096
|
|
|
2253
4097
|
compileSliceRange(start, len, sourceLen, bounds, context) {
|
|
@@ -2324,6 +4168,7 @@ class MirCompiler {
|
|
|
2324
4168
|
this.fail("slice array source must have primitive elements");
|
|
2325
4169
|
}
|
|
2326
4170
|
return [
|
|
4171
|
+
this.placeAddress(source.data, context),
|
|
2327
4172
|
this.placeAddress(source.data, context),
|
|
2328
4173
|
this.module.i32.const(type.data.len),
|
|
2329
4174
|
this.module.i32.const(this.scalarSize(element.data)),
|
|
@@ -2333,81 +4178,229 @@ class MirCompiler {
|
|
|
2333
4178
|
const item = this.constLayout[source.data];
|
|
2334
4179
|
if (!item) this.fail(`const data id ${source.data} is out of range`);
|
|
2335
4180
|
return [
|
|
4181
|
+
this.module.i32.const(item.address),
|
|
2336
4182
|
this.module.i32.const(item.address),
|
|
2337
4183
|
this.module.i32.const(item.len),
|
|
2338
4184
|
this.module.i32.const(this.scalarSize(item.scalar)),
|
|
2339
4185
|
];
|
|
2340
4186
|
}
|
|
2341
4187
|
if (source.kind === "buffer") {
|
|
2342
|
-
const buffer = this.
|
|
4188
|
+
const buffer = this.requireBufferRef(source.data.buffer);
|
|
2343
4189
|
const elementSize = this.scalarSize(buffer.element);
|
|
2344
|
-
const
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
return [
|
|
2351
|
-
address,
|
|
2352
|
-
this.compileBufferLen(source.data.buffer),
|
|
2353
|
-
this.module.i32.const(elementSize),
|
|
2354
|
-
];
|
|
2355
|
-
}
|
|
2356
|
-
const channels = () =>
|
|
2357
|
-
this.loadBufferTableValue(
|
|
2358
|
-
POINTER_GLOBALS.bufferChannels,
|
|
2359
|
-
source.data.buffer,
|
|
4190
|
+
const staticIndex = this.staticBufferRefIndex(source.data.buffer);
|
|
4191
|
+
const prelude = [];
|
|
4192
|
+
let descriptorIndex;
|
|
4193
|
+
if (staticIndex === null) {
|
|
4194
|
+
const indexLocal = this.allocateGeneratedLocal(
|
|
4195
|
+
context,
|
|
2360
4196
|
"i32",
|
|
4197
|
+
"buffer.descriptor_index",
|
|
4198
|
+
);
|
|
4199
|
+
prelude.push(
|
|
4200
|
+
this.module.local.set(
|
|
4201
|
+
indexLocal,
|
|
4202
|
+
this.compileBufferRefIndex(source.data.buffer, context),
|
|
4203
|
+
),
|
|
2361
4204
|
);
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
4205
|
+
descriptorIndex = () =>
|
|
4206
|
+
this.module.local.get(indexLocal, binaryen.i32);
|
|
4207
|
+
} else {
|
|
4208
|
+
descriptorIndex = () => this.module.i32.const(staticIndex);
|
|
4209
|
+
}
|
|
4210
|
+
const component = (globalName, scalar) => this.bufferTableValueFactory(
|
|
4211
|
+
globalName,
|
|
4212
|
+
descriptorIndex,
|
|
4213
|
+
staticIndex,
|
|
4214
|
+
scalar,
|
|
2366
4215
|
context,
|
|
2367
|
-
true,
|
|
2368
4216
|
);
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
4217
|
+
let channels = this.bufferChannelsFactory(
|
|
4218
|
+
source.data.buffer,
|
|
4219
|
+
descriptorIndex,
|
|
4220
|
+
staticIndex,
|
|
4221
|
+
context,
|
|
4222
|
+
);
|
|
4223
|
+
if (
|
|
4224
|
+
staticIndex === null
|
|
4225
|
+
&& this.bufferRefChannelMetadata(source.data.buffer).kind === "dynamic"
|
|
4226
|
+
) {
|
|
4227
|
+
channels = this.snapshotOperationValue(
|
|
4228
|
+
channels,
|
|
2377
4229
|
"i32",
|
|
2378
|
-
|
|
2379
|
-
|
|
4230
|
+
"buffer.channels",
|
|
4231
|
+
prelude,
|
|
4232
|
+
context,
|
|
4233
|
+
);
|
|
4234
|
+
}
|
|
4235
|
+
const readAddress = component(POINTER_GLOBALS.buffers, "i32");
|
|
4236
|
+
const writeAddress = component(POINTER_GLOBALS.bufferWrites, "i32");
|
|
4237
|
+
let read = readAddress();
|
|
4238
|
+
let write = writeAddress();
|
|
4239
|
+
if (source.data.channel !== null) {
|
|
4240
|
+
const channelLocal = this.allocateGeneratedLocal(
|
|
4241
|
+
context,
|
|
4242
|
+
"i32",
|
|
4243
|
+
"buffer.slice_channel",
|
|
4244
|
+
);
|
|
4245
|
+
prelude.push(
|
|
4246
|
+
this.module.local.set(
|
|
4247
|
+
channelLocal,
|
|
4248
|
+
this.compileDynamicBoundedIndex(
|
|
4249
|
+
() => this.compileValue(source.data.channel, context),
|
|
4250
|
+
channels,
|
|
4251
|
+
"clamp",
|
|
4252
|
+
context,
|
|
4253
|
+
true,
|
|
4254
|
+
),
|
|
4255
|
+
),
|
|
4256
|
+
);
|
|
4257
|
+
const channelOffset = () => this.module.i32.mul(
|
|
4258
|
+
this.module.local.get(channelLocal, binaryen.i32),
|
|
4259
|
+
this.module.i32.const(elementSize),
|
|
4260
|
+
);
|
|
4261
|
+
read = this.bufferPointerWithOffset(
|
|
4262
|
+
readAddress,
|
|
4263
|
+
channelOffset(),
|
|
4264
|
+
false,
|
|
4265
|
+
context,
|
|
4266
|
+
);
|
|
4267
|
+
write = this.bufferPointerWithOffset(
|
|
4268
|
+
writeAddress,
|
|
4269
|
+
channelOffset(),
|
|
4270
|
+
true,
|
|
4271
|
+
context,
|
|
4272
|
+
);
|
|
4273
|
+
}
|
|
4274
|
+
const result = [
|
|
4275
|
+
read,
|
|
4276
|
+
write,
|
|
4277
|
+
component(POINTER_GLOBALS.bufferFrames, "i32")(),
|
|
4278
|
+
source.data.channel === null
|
|
4279
|
+
? this.module.i32.const(elementSize)
|
|
4280
|
+
: this.module.i32.mul(channels(), this.module.i32.const(elementSize)),
|
|
2380
4281
|
];
|
|
4282
|
+
if (prelude.length > 0) {
|
|
4283
|
+
result[0] = this.module.block(
|
|
4284
|
+
null,
|
|
4285
|
+
[...prelude, result[0]],
|
|
4286
|
+
binaryen.i32,
|
|
4287
|
+
);
|
|
4288
|
+
}
|
|
4289
|
+
return result;
|
|
2381
4290
|
}
|
|
2382
4291
|
if (source.kind === "buffer_param") {
|
|
2383
4292
|
const type = this.bufferParamType(source.data.parameter, context);
|
|
2384
4293
|
const elementSize = this.scalarSize(type.data.element);
|
|
2385
|
-
const
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
4294
|
+
const staticSelector = this.staticBufferParamSelector(
|
|
4295
|
+
source.data.parameter,
|
|
4296
|
+
context,
|
|
4297
|
+
);
|
|
4298
|
+
const prelude = [];
|
|
4299
|
+
let selector = null;
|
|
4300
|
+
if (
|
|
4301
|
+
source.data.parameter?.kind === "array_element"
|
|
4302
|
+
&& staticSelector === null
|
|
4303
|
+
) {
|
|
4304
|
+
const selectorLocal = this.allocateGeneratedLocal(
|
|
4305
|
+
context,
|
|
4306
|
+
"i32",
|
|
4307
|
+
"buffer_param.selector",
|
|
4308
|
+
);
|
|
4309
|
+
prelude.push(
|
|
4310
|
+
this.module.local.set(
|
|
4311
|
+
selectorLocal,
|
|
4312
|
+
this.compileBufferParamSelector(source.data.parameter, context),
|
|
4313
|
+
),
|
|
4314
|
+
);
|
|
4315
|
+
selector = () => this.module.local.get(selectorLocal, binaryen.i32);
|
|
4316
|
+
} else if (staticSelector !== null) {
|
|
4317
|
+
selector = () => this.module.i32.const(staticSelector);
|
|
2393
4318
|
}
|
|
2394
|
-
const
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
4319
|
+
const component = (offset, scalar) => this.bufferParamComponentFactory(
|
|
4320
|
+
source.data.parameter,
|
|
4321
|
+
offset,
|
|
4322
|
+
scalar,
|
|
4323
|
+
selector,
|
|
4324
|
+
staticSelector,
|
|
2400
4325
|
context,
|
|
2401
|
-
true,
|
|
2402
4326
|
);
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
4327
|
+
let channels = this.bufferParamChannelsFactory(
|
|
4328
|
+
source.data.parameter,
|
|
4329
|
+
selector,
|
|
4330
|
+
staticSelector,
|
|
4331
|
+
context,
|
|
4332
|
+
);
|
|
4333
|
+
if (
|
|
4334
|
+
source.data.parameter?.kind === "array_element"
|
|
4335
|
+
&& staticSelector === null
|
|
4336
|
+
&& this.bufferChannelMetadata(
|
|
4337
|
+
type.data.channels,
|
|
4338
|
+
type.data.element,
|
|
4339
|
+
).kind === "dynamic"
|
|
4340
|
+
) {
|
|
4341
|
+
channels = this.snapshotOperationValue(
|
|
4342
|
+
channels,
|
|
4343
|
+
"i32",
|
|
4344
|
+
"buffer_param.channels",
|
|
4345
|
+
prelude,
|
|
4346
|
+
context,
|
|
4347
|
+
);
|
|
4348
|
+
}
|
|
4349
|
+
const readAddress = component(0, "i32");
|
|
4350
|
+
const writeAddress = component(1, "i32");
|
|
4351
|
+
let read = readAddress();
|
|
4352
|
+
let write = writeAddress();
|
|
4353
|
+
if (source.data.channel !== null) {
|
|
4354
|
+
const channelLocal = this.allocateGeneratedLocal(
|
|
4355
|
+
context,
|
|
4356
|
+
"i32",
|
|
4357
|
+
"buffer_param.slice_channel",
|
|
4358
|
+
);
|
|
4359
|
+
prelude.push(
|
|
4360
|
+
this.module.local.set(
|
|
4361
|
+
channelLocal,
|
|
4362
|
+
this.compileDynamicBoundedIndex(
|
|
4363
|
+
() => this.compileValue(source.data.channel, context),
|
|
4364
|
+
channels,
|
|
4365
|
+
"clamp",
|
|
4366
|
+
context,
|
|
4367
|
+
true,
|
|
4368
|
+
),
|
|
4369
|
+
),
|
|
4370
|
+
);
|
|
4371
|
+
const channelOffset = () => this.module.i32.mul(
|
|
4372
|
+
this.module.local.get(channelLocal, binaryen.i32),
|
|
4373
|
+
this.module.i32.const(elementSize),
|
|
4374
|
+
);
|
|
4375
|
+
read = this.bufferPointerWithOffset(
|
|
4376
|
+
readAddress,
|
|
4377
|
+
channelOffset(),
|
|
4378
|
+
false,
|
|
4379
|
+
context,
|
|
4380
|
+
);
|
|
4381
|
+
write = this.bufferPointerWithOffset(
|
|
4382
|
+
writeAddress,
|
|
4383
|
+
channelOffset(),
|
|
4384
|
+
true,
|
|
4385
|
+
context,
|
|
4386
|
+
);
|
|
4387
|
+
}
|
|
4388
|
+
const result = [
|
|
4389
|
+
read,
|
|
4390
|
+
write,
|
|
4391
|
+
component(2, "i32")(),
|
|
4392
|
+
source.data.channel === null
|
|
4393
|
+
? this.module.i32.const(elementSize)
|
|
4394
|
+
: this.module.i32.mul(channels(), this.module.i32.const(elementSize)),
|
|
2410
4395
|
];
|
|
4396
|
+
if (prelude.length > 0) {
|
|
4397
|
+
result[0] = this.module.block(
|
|
4398
|
+
null,
|
|
4399
|
+
[...prelude, result[0]],
|
|
4400
|
+
binaryen.i32,
|
|
4401
|
+
);
|
|
4402
|
+
}
|
|
4403
|
+
return result;
|
|
2411
4404
|
}
|
|
2412
4405
|
this.fail(`unsupported slice source '${String(source.kind)}'`);
|
|
2413
4406
|
}
|
|
@@ -2432,25 +4425,26 @@ class MirCompiler {
|
|
|
2432
4425
|
return type.data.access;
|
|
2433
4426
|
}
|
|
2434
4427
|
|
|
2435
|
-
compileSliceAddress(slice, index, bounds, context) {
|
|
4428
|
+
compileSliceAddress(slice, index, bounds, context, write) {
|
|
2436
4429
|
return this.compileSliceAddressWithFactories(
|
|
2437
4430
|
() => this.compileSliceValue(slice, context),
|
|
2438
4431
|
() => this.compileValue(index, context),
|
|
2439
4432
|
bounds,
|
|
2440
4433
|
context,
|
|
4434
|
+
write,
|
|
2441
4435
|
);
|
|
2442
4436
|
}
|
|
2443
4437
|
|
|
2444
|
-
compileSliceAddressWithFactories(slice, index, bounds, context) {
|
|
4438
|
+
compileSliceAddressWithFactories(slice, index, bounds, context, write) {
|
|
2445
4439
|
const bounded = this.compileDynamicBoundedIndex(
|
|
2446
4440
|
index,
|
|
2447
|
-
() => slice()[
|
|
4441
|
+
() => slice()[2],
|
|
2448
4442
|
bounds,
|
|
2449
4443
|
context,
|
|
2450
4444
|
);
|
|
2451
4445
|
return this.module.i32.add(
|
|
2452
|
-
slice()[0],
|
|
2453
|
-
this.module.i32.mul(bounded, slice()[
|
|
4446
|
+
slice()[write ? 1 : 0],
|
|
4447
|
+
this.module.i32.mul(bounded, slice()[3]),
|
|
2454
4448
|
);
|
|
2455
4449
|
}
|
|
2456
4450
|
|
|
@@ -2482,7 +4476,7 @@ class MirCompiler {
|
|
|
2482
4476
|
);
|
|
2483
4477
|
}
|
|
2484
4478
|
|
|
2485
|
-
compileSliceWindowAddress(data, parameterType, context) {
|
|
4479
|
+
compileSliceWindowAddress(data, parameterType, context, write) {
|
|
2486
4480
|
const elementType = this.type(parameterType.data.element);
|
|
2487
4481
|
if (elementType.kind !== "scalar") {
|
|
2488
4482
|
this.fail("slice-window fixed-array parameter element is not scalar");
|
|
@@ -2494,7 +4488,7 @@ class MirCompiler {
|
|
|
2494
4488
|
() => this.compileValue(data.start, context),
|
|
2495
4489
|
() =>
|
|
2496
4490
|
this.module.i32.sub(
|
|
2497
|
-
slice()[
|
|
4491
|
+
slice()[2],
|
|
2498
4492
|
this.module.i32.const(requiredLen),
|
|
2499
4493
|
),
|
|
2500
4494
|
data.bounds,
|
|
@@ -2502,8 +4496,8 @@ class MirCompiler {
|
|
|
2502
4496
|
);
|
|
2503
4497
|
const address = () =>
|
|
2504
4498
|
this.module.i32.add(
|
|
2505
|
-
slice()[0],
|
|
2506
|
-
this.module.i32.mul(start(), slice()[
|
|
4499
|
+
slice()[write ? 1 : 0],
|
|
4500
|
+
this.module.i32.mul(start(), slice()[3]),
|
|
2507
4501
|
);
|
|
2508
4502
|
if (data.bounds === "unchecked") {
|
|
2509
4503
|
return address();
|
|
@@ -2511,11 +4505,11 @@ class MirCompiler {
|
|
|
2511
4505
|
const invalidShape = () =>
|
|
2512
4506
|
this.module.i32.or(
|
|
2513
4507
|
this.module.i32.ne(
|
|
2514
|
-
slice()[
|
|
4508
|
+
slice()[3],
|
|
2515
4509
|
this.module.i32.const(elementSize),
|
|
2516
4510
|
),
|
|
2517
4511
|
this.module.i32.lt_s(
|
|
2518
|
-
slice()[
|
|
4512
|
+
slice()[2],
|
|
2519
4513
|
this.module.i32.const(requiredLen),
|
|
2520
4514
|
),
|
|
2521
4515
|
);
|
|
@@ -2564,7 +4558,7 @@ class MirCompiler {
|
|
|
2564
4558
|
const scalar = this.sliceElementScalar(data.slice, context);
|
|
2565
4559
|
return this.loadScalar(
|
|
2566
4560
|
scalar,
|
|
2567
|
-
this.compileSliceAddress(data.slice, data.index, data.bounds, context),
|
|
4561
|
+
this.compileSliceAddress(data.slice, data.index, data.bounds, context, false),
|
|
2568
4562
|
);
|
|
2569
4563
|
}
|
|
2570
4564
|
|
|
@@ -2575,7 +4569,7 @@ class MirCompiler {
|
|
|
2575
4569
|
const scalar = this.sliceElementScalar(data.slice, context);
|
|
2576
4570
|
return this.storeScalar(
|
|
2577
4571
|
scalar,
|
|
2578
|
-
this.compileSliceAddress(data.slice, data.index, data.bounds, context),
|
|
4572
|
+
this.compileSliceAddress(data.slice, data.index, data.bounds, context, true),
|
|
2579
4573
|
this.compileValue(data.value, context),
|
|
2580
4574
|
);
|
|
2581
4575
|
}
|
|
@@ -2598,14 +4592,14 @@ class MirCompiler {
|
|
|
2598
4592
|
const scalarSize = this.scalarSize(scalar);
|
|
2599
4593
|
const address = () =>
|
|
2600
4594
|
this.module.i32.add(
|
|
2601
|
-
destination()[
|
|
2602
|
-
this.module.i32.mul(counterValue(), destination()[
|
|
4595
|
+
destination()[1],
|
|
4596
|
+
this.module.i32.mul(counterValue(), destination()[3]),
|
|
2603
4597
|
);
|
|
2604
4598
|
const scalarLoop = () =>
|
|
2605
4599
|
this.module.loop(
|
|
2606
4600
|
scalarLoopLabel,
|
|
2607
4601
|
this.module.if(
|
|
2608
|
-
this.module.i32.lt_s(counterValue(), destination()[
|
|
4602
|
+
this.module.i32.lt_s(counterValue(), destination()[2]),
|
|
2609
4603
|
this.module.block(null, [
|
|
2610
4604
|
this.storeScalar(
|
|
2611
4605
|
scalar,
|
|
@@ -2625,18 +4619,18 @@ class MirCompiler {
|
|
|
2625
4619
|
const lanes = 16 / scalarSize;
|
|
2626
4620
|
const vectorCondition = this.module.i32.and(
|
|
2627
4621
|
this.module.i32.eq(
|
|
2628
|
-
destination()[
|
|
4622
|
+
destination()[3],
|
|
2629
4623
|
this.module.i32.const(scalarSize),
|
|
2630
4624
|
),
|
|
2631
4625
|
this.module.i32.and(
|
|
2632
4626
|
this.module.i32.ge_u(
|
|
2633
|
-
destination()[
|
|
4627
|
+
destination()[2],
|
|
2634
4628
|
this.module.i32.const(lanes),
|
|
2635
4629
|
),
|
|
2636
4630
|
this.module.i32.le_u(
|
|
2637
4631
|
counterValue(),
|
|
2638
4632
|
this.module.i32.sub(
|
|
2639
|
-
destination()[
|
|
4633
|
+
destination()[2],
|
|
2640
4634
|
this.module.i32.const(lanes),
|
|
2641
4635
|
),
|
|
2642
4636
|
),
|
|
@@ -2696,8 +4690,8 @@ class MirCompiler {
|
|
|
2696
4690
|
const copyIndex = () =>
|
|
2697
4691
|
this.module.select(
|
|
2698
4692
|
this.module.i32.and(
|
|
2699
|
-
this.module.i32.eq(destination()[
|
|
2700
|
-
this.module.i32.gt_u(destination()[
|
|
4693
|
+
this.module.i32.eq(destination()[3], source()[3]),
|
|
4694
|
+
this.module.i32.gt_u(destination()[1], source()[0]),
|
|
2701
4695
|
),
|
|
2702
4696
|
this.module.i32.sub(
|
|
2703
4697
|
this.module.i32.sub(countValue(), this.module.i32.const(1)),
|
|
@@ -2708,12 +4702,12 @@ class MirCompiler {
|
|
|
2708
4702
|
const sourceAddress = () =>
|
|
2709
4703
|
this.module.i32.add(
|
|
2710
4704
|
source()[0],
|
|
2711
|
-
this.module.i32.mul(copyIndex(), source()[
|
|
4705
|
+
this.module.i32.mul(copyIndex(), source()[3]),
|
|
2712
4706
|
);
|
|
2713
4707
|
const destinationAddress = () =>
|
|
2714
4708
|
this.module.i32.add(
|
|
2715
|
-
destination()[
|
|
2716
|
-
this.module.i32.mul(copyIndex(), destination()[
|
|
4709
|
+
destination()[1],
|
|
4710
|
+
this.module.i32.mul(copyIndex(), destination()[3]),
|
|
2717
4711
|
);
|
|
2718
4712
|
const sourceScalar = this.sliceElementScalar(data.source, context);
|
|
2719
4713
|
const destinationScalar = this.sliceElementScalar(data.destination, context);
|
|
@@ -2731,7 +4725,7 @@ class MirCompiler {
|
|
|
2731
4725
|
source()[0],
|
|
2732
4726
|
this.module.i32.mul(
|
|
2733
4727
|
this.module.i32.sub(countValue(), this.module.i32.const(1)),
|
|
2734
|
-
source()[
|
|
4728
|
+
source()[3],
|
|
2735
4729
|
),
|
|
2736
4730
|
),
|
|
2737
4731
|
this.module.i32.const(this.scalarSize(sourceScalar)),
|
|
@@ -2739,10 +4733,10 @@ class MirCompiler {
|
|
|
2739
4733
|
const destinationEnd = () =>
|
|
2740
4734
|
this.module.i32.add(
|
|
2741
4735
|
this.module.i32.add(
|
|
2742
|
-
destination()[
|
|
4736
|
+
destination()[1],
|
|
2743
4737
|
this.module.i32.mul(
|
|
2744
4738
|
this.module.i32.sub(countValue(), this.module.i32.const(1)),
|
|
2745
|
-
destination()[
|
|
4739
|
+
destination()[3],
|
|
2746
4740
|
),
|
|
2747
4741
|
),
|
|
2748
4742
|
this.module.i32.const(this.scalarSize(destinationScalar)),
|
|
@@ -2751,13 +4745,13 @@ class MirCompiler {
|
|
|
2751
4745
|
this.module.i32.and(
|
|
2752
4746
|
nonEmpty(),
|
|
2753
4747
|
this.module.i32.and(
|
|
2754
|
-
this.module.i32.lt_u(destination()[
|
|
4748
|
+
this.module.i32.lt_u(destination()[1], sourceEnd()),
|
|
2755
4749
|
this.module.i32.lt_u(source()[0], destinationEnd()),
|
|
2756
4750
|
),
|
|
2757
4751
|
);
|
|
2758
4752
|
const invalidOverlap = () =>
|
|
2759
4753
|
this.module.i32.and(
|
|
2760
|
-
this.module.i32.ne(destination()[
|
|
4754
|
+
this.module.i32.ne(destination()[3], source()[3]),
|
|
2761
4755
|
overlaps(),
|
|
2762
4756
|
);
|
|
2763
4757
|
const scalarCopy = () =>
|
|
@@ -2780,18 +4774,18 @@ class MirCompiler {
|
|
|
2780
4774
|
? this.module.if(
|
|
2781
4775
|
this.module.i32.and(
|
|
2782
4776
|
this.module.i32.eq(
|
|
2783
|
-
destination()[
|
|
4777
|
+
destination()[3],
|
|
2784
4778
|
this.module.i32.const(this.scalarSize(destinationScalar)),
|
|
2785
4779
|
),
|
|
2786
4780
|
this.module.i32.eq(
|
|
2787
|
-
source()[
|
|
4781
|
+
source()[3],
|
|
2788
4782
|
this.module.i32.const(this.scalarSize(sourceScalar)),
|
|
2789
4783
|
),
|
|
2790
4784
|
),
|
|
2791
4785
|
// memory.copy has memmove overlap semantics and lets engines use
|
|
2792
4786
|
// their tuned bulk-memory implementation for contiguous slices.
|
|
2793
4787
|
this.module.memory.copy(
|
|
2794
|
-
destination()[
|
|
4788
|
+
destination()[1],
|
|
2795
4789
|
source()[0],
|
|
2796
4790
|
this.module.i32.mul(
|
|
2797
4791
|
countValue(),
|
|
@@ -2805,9 +4799,9 @@ class MirCompiler {
|
|
|
2805
4799
|
this.module.local.set(
|
|
2806
4800
|
count,
|
|
2807
4801
|
this.module.select(
|
|
2808
|
-
this.module.i32.lt_s(destination()[
|
|
2809
|
-
destination()[
|
|
2810
|
-
source()[
|
|
4802
|
+
this.module.i32.lt_s(destination()[2], source()[2]),
|
|
4803
|
+
destination()[2],
|
|
4804
|
+
source()[2],
|
|
2811
4805
|
),
|
|
2812
4806
|
),
|
|
2813
4807
|
this.module.if(invalidOverlap(), this.raiseRuntimeFailure(context)),
|
|
@@ -3431,6 +5425,12 @@ class MirCompiler {
|
|
|
3431
5425
|
lhs.data.element === rhs.data.element &&
|
|
3432
5426
|
JSON.stringify(lhs.data.channels) === JSON.stringify(rhs.data.channels) &&
|
|
3433
5427
|
lhs.data.access === rhs.data.access;
|
|
5428
|
+
} else if (lhs.kind === "buffer_span") {
|
|
5429
|
+
equivalent =
|
|
5430
|
+
lhs.data.element === rhs.data.element &&
|
|
5431
|
+
JSON.stringify(lhs.data.channels) === JSON.stringify(rhs.data.channels) &&
|
|
5432
|
+
lhs.data.access === rhs.data.access &&
|
|
5433
|
+
lhs.data.len === rhs.data.len;
|
|
3434
5434
|
} else if (lhs.kind === "tuple") {
|
|
3435
5435
|
equivalent =
|
|
3436
5436
|
lhs.data.length === rhs.data.length &&
|
|
@@ -3537,6 +5537,85 @@ class MirCompiler {
|
|
|
3537
5537
|
return this.mir.interface.buffers[id];
|
|
3538
5538
|
}
|
|
3539
5539
|
|
|
5540
|
+
bufferRefFirst(bufferRef) {
|
|
5541
|
+
if (Number.isInteger(bufferRef)) return bufferRef;
|
|
5542
|
+
if (bufferRef?.kind === "direct" && Number.isInteger(bufferRef.data)) {
|
|
5543
|
+
return bufferRef.data;
|
|
5544
|
+
}
|
|
5545
|
+
if (
|
|
5546
|
+
bufferRef?.kind === "array_element"
|
|
5547
|
+
&& Number.isInteger(bufferRef.data?.first)
|
|
5548
|
+
&& Number.isInteger(bufferRef.data?.len)
|
|
5549
|
+
&& bufferRef.data.len > 0
|
|
5550
|
+
) {
|
|
5551
|
+
return bufferRef.data.first;
|
|
5552
|
+
}
|
|
5553
|
+
this.fail("invalid MIR buffer reference");
|
|
5554
|
+
}
|
|
5555
|
+
|
|
5556
|
+
requireBufferRef(bufferRef) {
|
|
5557
|
+
const first = this.bufferRefFirst(bufferRef);
|
|
5558
|
+
const buffer = this.requireBuffer(first);
|
|
5559
|
+
if (bufferRef?.kind === "array_element") {
|
|
5560
|
+
const last = first + bufferRef.data.len - 1;
|
|
5561
|
+
this.requireBuffer(last);
|
|
5562
|
+
for (let id = first + 1; id <= last; id += 1) {
|
|
5563
|
+
const candidate = this.requireBuffer(id);
|
|
5564
|
+
if (
|
|
5565
|
+
candidate.element !== buffer.element
|
|
5566
|
+
|| JSON.stringify(candidate.channels) !== JSON.stringify(buffer.channels)
|
|
5567
|
+
|| candidate.access !== buffer.access
|
|
5568
|
+
) {
|
|
5569
|
+
this.fail("buffer-array elements must have one descriptor type");
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
return buffer;
|
|
5574
|
+
}
|
|
5575
|
+
|
|
5576
|
+
bufferRefChannelMetadata(bufferRef) {
|
|
5577
|
+
const buffer = this.requireBufferRef(bufferRef);
|
|
5578
|
+
return this.bufferChannelMetadata(buffer.channels, buffer.element);
|
|
5579
|
+
}
|
|
5580
|
+
|
|
5581
|
+
compileBufferRefIndex(bufferRef, context) {
|
|
5582
|
+
const staticIndex = this.staticBufferRefIndex(bufferRef);
|
|
5583
|
+
if (staticIndex !== null) return this.module.i32.const(staticIndex);
|
|
5584
|
+
const first = this.bufferRefFirst(bufferRef);
|
|
5585
|
+
const data = bufferRef.data;
|
|
5586
|
+
const selector = this.compileDynamicBoundedIndex(
|
|
5587
|
+
() => this.compileValue(data.selector, context),
|
|
5588
|
+
() => this.module.i32.const(data.len),
|
|
5589
|
+
data.bounds,
|
|
5590
|
+
context,
|
|
5591
|
+
true,
|
|
5592
|
+
);
|
|
5593
|
+
return this.module.i32.add(this.module.i32.const(first), selector);
|
|
5594
|
+
}
|
|
5595
|
+
|
|
5596
|
+
staticBufferRefIndex(bufferRef) {
|
|
5597
|
+
const first = this.bufferRefFirst(bufferRef);
|
|
5598
|
+
if (Number.isInteger(bufferRef) || bufferRef.kind === "direct") return first;
|
|
5599
|
+
const data = bufferRef.data;
|
|
5600
|
+
const selector = data.selector;
|
|
5601
|
+
if (
|
|
5602
|
+
selector?.kind !== "constant"
|
|
5603
|
+
|| selector.data?.type !== "i32"
|
|
5604
|
+
|| !Number.isInteger(selector.data.value)
|
|
5605
|
+
) {
|
|
5606
|
+
return null;
|
|
5607
|
+
}
|
|
5608
|
+
let index = selector.data.value;
|
|
5609
|
+
if (data.bounds === "clamp") {
|
|
5610
|
+
index = Math.min(data.len - 1, Math.max(0, index));
|
|
5611
|
+
} else if (index < 0 || index >= data.len) {
|
|
5612
|
+
// Preserve checked failure behavior and leave invalid unchecked MIR to
|
|
5613
|
+
// the normal validated lowering path.
|
|
5614
|
+
return null;
|
|
5615
|
+
}
|
|
5616
|
+
return first + index;
|
|
5617
|
+
}
|
|
5618
|
+
|
|
3540
5619
|
currentLabel(labels, statement) {
|
|
3541
5620
|
const label = labels.at(-1);
|
|
3542
5621
|
if (!label) this.fail(`'${statement}' appears outside a MIR loop`);
|
|
@@ -3669,8 +5748,8 @@ class MirCompiler {
|
|
|
3669
5748
|
}
|
|
3670
5749
|
: null,
|
|
3671
5750
|
})),
|
|
3672
|
-
buffers: this.mir.interface.buffers.map((buffer) => {
|
|
3673
|
-
const channels = this.bufferChannelMetadata(buffer.channels);
|
|
5751
|
+
buffers: this.mir.interface.buffers.map((buffer, bufferId) => {
|
|
5752
|
+
const channels = this.bufferChannelMetadata(buffer.channels, buffer.element);
|
|
3674
5753
|
return {
|
|
3675
5754
|
name: buffer.name,
|
|
3676
5755
|
type_repr: this.bufferTypeRepr(buffer, channels),
|
|
@@ -3679,9 +5758,14 @@ class MirCompiler {
|
|
|
3679
5758
|
channels: channels.kind,
|
|
3680
5759
|
static_channels: channels.count,
|
|
3681
5760
|
access: buffer.access,
|
|
3682
|
-
may_write:
|
|
5761
|
+
may_write: this.bufferMayWrite[bufferId],
|
|
3683
5762
|
};
|
|
3684
5763
|
}),
|
|
5764
|
+
buffer_arrays: (this.mir.interface.buffer_arrays ?? []).map((array) => ({
|
|
5765
|
+
name: array.name,
|
|
5766
|
+
first_buffer: array.first,
|
|
5767
|
+
len: array.len,
|
|
5768
|
+
})),
|
|
3685
5769
|
events: this.mir.interface.events.map((event, eventId) => ({
|
|
3686
5770
|
name: event.name,
|
|
3687
5771
|
export: `onda_event_${eventId}`,
|
|
@@ -3773,11 +5857,11 @@ class MirCompiler {
|
|
|
3773
5857
|
}
|
|
3774
5858
|
|
|
3775
5859
|
bufferTypeRepr(buffer, channels) {
|
|
3776
|
-
if (channels.kind === "mono") return `buffer
|
|
5860
|
+
if (channels.kind === "mono") return `buffer<${buffer.element}>`;
|
|
3777
5861
|
if (channels.kind === "static") {
|
|
3778
|
-
return `buffer
|
|
5862
|
+
return `buffer<${buffer.element}[${channels.count}]>`;
|
|
3779
5863
|
}
|
|
3780
|
-
return `buffer
|
|
5864
|
+
return `buffer<${buffer.element}[]>`;
|
|
3781
5865
|
}
|
|
3782
5866
|
|
|
3783
5867
|
storageShape(typeId) {
|
|
@@ -3803,7 +5887,7 @@ class MirCompiler {
|
|
|
3803
5887
|
this.fail(`storage metadata for MIR type '${type.kind}' is not supported yet`);
|
|
3804
5888
|
}
|
|
3805
5889
|
|
|
3806
|
-
bufferChannelMetadata(channels) {
|
|
5890
|
+
bufferChannelMetadata(channels, element) {
|
|
3807
5891
|
if (channels === "mono") {
|
|
3808
5892
|
return { kind: "mono", count: 1 };
|
|
3809
5893
|
}
|
|
@@ -3814,7 +5898,8 @@ class MirCompiler {
|
|
|
3814
5898
|
channels &&
|
|
3815
5899
|
typeof channels === "object" &&
|
|
3816
5900
|
Number.isInteger(channels.static) &&
|
|
3817
|
-
channels.static > 0
|
|
5901
|
+
channels.static > 0 &&
|
|
5902
|
+
channels.static <= Math.floor(0x7fffffff / this.scalarSize(element))
|
|
3818
5903
|
) {
|
|
3819
5904
|
return { kind: "static", count: channels.static };
|
|
3820
5905
|
}
|