@onda-lang/wasm-compiler 0.5.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -7
- package/bin/onda-wasm.js +1 -1
- package/dist/backend/artifact.d.ts +2 -0
- package/dist/backend/constants.js +1 -1
- package/dist/backend/index.d.ts +2 -0
- package/dist/backend/index.js +2639 -317
- package/dist/build.json +3 -3
- package/dist/frontend/README.md +20 -7
- package/dist/frontend/onda_compiler_web.d.ts +120 -9
- package/dist/frontend/onda_compiler_web.js +556 -42
- package/dist/frontend/onda_compiler_web_bg.wasm +0 -0
- package/dist/frontend/onda_compiler_web_bg.wasm.d.ts +42 -5
- package/dist/frontend/package.json +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/index.d.ts +119 -5
- package/src/index.js +455 -26
- package/src/worker.js +63 -6
package/dist/backend/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
PROCESSOR_ABI_VERSION,
|
|
9
9
|
PROCESSOR_ARTIFACT_FORMAT,
|
|
10
10
|
PROCESSOR_ARTIFACT_FORMAT_VERSION,
|
|
11
|
+
PROCESSOR_EXECUTION_OK,
|
|
12
|
+
PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE,
|
|
11
13
|
PROCESSOR_SNAPSHOT_FORMAT_VERSION,
|
|
12
14
|
validateProcessorMetadata,
|
|
13
15
|
} from "./artifact.js";
|
|
@@ -17,6 +19,8 @@ export {
|
|
|
17
19
|
PROCESSOR_ABI_VERSION,
|
|
18
20
|
PROCESSOR_ARTIFACT_FORMAT,
|
|
19
21
|
PROCESSOR_ARTIFACT_FORMAT_VERSION,
|
|
22
|
+
PROCESSOR_EXECUTION_OK,
|
|
23
|
+
PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE,
|
|
20
24
|
PROCESSOR_SNAPSHOT_FORMAT_VERSION,
|
|
21
25
|
createProcessorArtifactFiles,
|
|
22
26
|
loadProcessorArtifactFiles,
|
|
@@ -38,6 +42,7 @@ const MAX_MEMORY_PAGES = 65_536;
|
|
|
38
42
|
const WASM32_ADDRESS_SPACE_BYTES = MAX_MEMORY_PAGES * PAGE_BYTES;
|
|
39
43
|
const DEFAULT_OPTIMIZE_LEVEL = 4;
|
|
40
44
|
const ONDA_PROCESS_FULL_BLOCK = (1 << 0) | (1 << 1);
|
|
45
|
+
const RUNTIME_FAILURE_GLOBAL = "$onda.runtime_failure";
|
|
41
46
|
const MATH_KERNEL_INTRINSICS = new Set([
|
|
42
47
|
"sin",
|
|
43
48
|
"cos",
|
|
@@ -59,10 +64,38 @@ const POINTER_GLOBALS = Object.freeze({
|
|
|
59
64
|
state: "$onda.state",
|
|
60
65
|
eventPayload: "$onda.event_payload",
|
|
61
66
|
buffers: "$onda.buffers",
|
|
67
|
+
bufferWrites: "$onda.buffer_writes",
|
|
62
68
|
bufferFrames: "$onda.buffer_frames",
|
|
63
69
|
bufferChannels: "$onda.buffer_channels",
|
|
64
70
|
bufferSampleRates: "$onda.buffer_sample_rates",
|
|
65
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
|
+
]);
|
|
66
99
|
|
|
67
100
|
// Compiles MIR emitted by Onda's semantic producer. The producer owns proofs
|
|
68
101
|
// for operations marked `bounds: "unchecked"` and all other validated MIR
|
|
@@ -179,6 +212,11 @@ class MirCompiler {
|
|
|
179
212
|
? MATH_KERNEL_RESERVED_END
|
|
180
213
|
: STATIC_BASE;
|
|
181
214
|
this.internalHelpers = new Set();
|
|
215
|
+
this.functionMayFail = [];
|
|
216
|
+
this.bufferMayWrite = [];
|
|
217
|
+
this.fallbackBufferReadAddress = 0;
|
|
218
|
+
this.fallbackBufferWriteAddress = 0;
|
|
219
|
+
this.scalarParameterByValue = [];
|
|
182
220
|
this.nextLabel = 0;
|
|
183
221
|
}
|
|
184
222
|
|
|
@@ -208,6 +246,21 @@ class MirCompiler {
|
|
|
208
246
|
this.options.allowInliningFunctionsWithLoops,
|
|
209
247
|
);
|
|
210
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
|
+
}
|
|
211
264
|
} finally {
|
|
212
265
|
binaryen.setOptimizeLevel(previousOptimizeLevel);
|
|
213
266
|
binaryen.setShrinkLevel(previousShrinkLevel);
|
|
@@ -238,6 +291,509 @@ class MirCompiler {
|
|
|
238
291
|
}
|
|
239
292
|
}
|
|
240
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
|
+
|
|
241
797
|
validateEnvelope() {
|
|
242
798
|
const mir = this.mir;
|
|
243
799
|
if (!mir || typeof mir !== "object" || Array.isArray(mir)) {
|
|
@@ -297,6 +853,127 @@ class MirCompiler {
|
|
|
297
853
|
this.validateCurrentSchemaEnvelope();
|
|
298
854
|
this.validateProcessEntrySignature();
|
|
299
855
|
this.validateAcyclicCallGraph();
|
|
856
|
+
this.analyzeBufferWrites();
|
|
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;
|
|
300
977
|
}
|
|
301
978
|
|
|
302
979
|
validateCurrentSchemaEnvelope() {
|
|
@@ -444,39 +1121,499 @@ class MirCompiler {
|
|
|
444
1121
|
}
|
|
445
1122
|
}
|
|
446
1123
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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 [];
|
|
1129
|
+
if (
|
|
1130
|
+
selector?.kind === "constant"
|
|
1131
|
+
&& selector.data?.type === "i32"
|
|
1132
|
+
&& Number.isInteger(selector.data.value)
|
|
1133
|
+
) {
|
|
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];
|
|
1140
|
+
}
|
|
1141
|
+
return Array.from({ length: len }, (_, slot) => slot);
|
|
1142
|
+
};
|
|
1143
|
+
const bufferIds = (bufferRef) => {
|
|
1144
|
+
if (Number.isInteger(bufferRef)) return [bufferRef];
|
|
1145
|
+
if (bufferRef?.kind === "direct" && Number.isInteger(bufferRef.data)) {
|
|
1146
|
+
return [bufferRef.data];
|
|
1147
|
+
}
|
|
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 }];
|
|
1169
|
+
}
|
|
1170
|
+
if (
|
|
1171
|
+
parameterRef?.kind === "direct"
|
|
1172
|
+
&& Number.isInteger(parameterRef.data)
|
|
1173
|
+
) {
|
|
1174
|
+
return [{ parameter: parameterRef.data, slot: 0 }];
|
|
1175
|
+
}
|
|
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 [];
|
|
1190
|
+
};
|
|
1191
|
+
const bufferParamOrigins = (parameterRef, func) => new Set(
|
|
1192
|
+
bufferParamSlots(parameterRef, func).map(({ parameter, slot }) =>
|
|
1193
|
+
parameterOrigin(parameter, slot)
|
|
1194
|
+
),
|
|
463
1195
|
);
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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)]);
|
|
1214
|
+
}
|
|
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
|
+
};
|
|
470
1377
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
this.
|
|
479
|
-
|
|
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);
|
|
1588
|
+
this.outputLayout = this.layoutPorts(this.mir.interface.outputs);
|
|
1589
|
+
this.controlOutputLayout = this.layoutControlOutputs();
|
|
1590
|
+
this.eventLayout = this.mir.interface.events.map((event) =>
|
|
1591
|
+
this.layoutEventValues(event.params),
|
|
1592
|
+
);
|
|
1593
|
+
this.requireWasm32Extent(
|
|
1594
|
+
this.stateLayout.byteLength,
|
|
1595
|
+
"MIR physical state storage",
|
|
1596
|
+
);
|
|
1597
|
+
this.requireWasm32Extent(
|
|
1598
|
+
this.paramLayout.byteLength,
|
|
1599
|
+
"MIR parameter storage",
|
|
1600
|
+
);
|
|
1601
|
+
for (const [eventId, layout] of this.eventLayout.entries()) {
|
|
1602
|
+
this.requireWasm32Extent(
|
|
1603
|
+
layout.minimumByteLength,
|
|
1604
|
+
`MIR event ${eventId} fixed payload storage`,
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
for (let id = 0; id < this.mir.const_data.length; id += 1) {
|
|
1609
|
+
const data = this.mir.const_data[id];
|
|
1610
|
+
const scalar = data.element;
|
|
1611
|
+
const size = this.scalarSize(scalar);
|
|
1612
|
+
this.nextStaticAddress = alignUp(this.nextStaticAddress, size);
|
|
1613
|
+
const address = this.nextStaticAddress;
|
|
1614
|
+
const bytes = encodeScalarValues(data.values, scalar, this);
|
|
1615
|
+
this.memorySegments.push({
|
|
1616
|
+
offset: this.module.i32.const(address),
|
|
480
1617
|
data: bytes,
|
|
481
1618
|
});
|
|
482
1619
|
this.constLayout.push({ address, scalar, len: data.values.length });
|
|
@@ -507,6 +1644,18 @@ class MirCompiler {
|
|
|
507
1644
|
return { address, scalar: type.data, size };
|
|
508
1645
|
});
|
|
509
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
|
+
}
|
|
510
1659
|
this.nextStaticAddress = alignUp(this.nextStaticAddress, 16);
|
|
511
1660
|
this.requireWasm32Extent(this.nextStaticAddress, "MIR static storage");
|
|
512
1661
|
this.requireWasm32Extent(
|
|
@@ -529,7 +1678,7 @@ class MirCompiler {
|
|
|
529
1678
|
const parameter = target?.params[index];
|
|
530
1679
|
const type = parameter && this.type(parameter.ty);
|
|
531
1680
|
if (
|
|
532
|
-
|
|
1681
|
+
this.parameterPassingMode(data.function, index) !== "value"
|
|
533
1682
|
&& type?.kind === "scalar"
|
|
534
1683
|
&& argument.kind === "place"
|
|
535
1684
|
&& argument.data.base.kind === "local"
|
|
@@ -663,6 +1812,12 @@ class MirCompiler {
|
|
|
663
1812
|
for (const name of Object.values(POINTER_GLOBALS)) {
|
|
664
1813
|
this.module.addGlobal(name, binaryen.i32, true, this.module.i32.const(0));
|
|
665
1814
|
}
|
|
1815
|
+
this.module.addGlobal(
|
|
1816
|
+
RUNTIME_FAILURE_GLOBAL,
|
|
1817
|
+
binaryen.i32,
|
|
1818
|
+
true,
|
|
1819
|
+
this.module.i32.const(0),
|
|
1820
|
+
);
|
|
666
1821
|
}
|
|
667
1822
|
|
|
668
1823
|
addMathKernel() {
|
|
@@ -752,13 +1907,13 @@ class MirCompiler {
|
|
|
752
1907
|
|
|
753
1908
|
addMirFunction(id, func) {
|
|
754
1909
|
let nextIndex = 0;
|
|
755
|
-
const paramLayouts = func.params.map((param) => {
|
|
1910
|
+
const paramLayouts = func.params.map((param, parameterId) => {
|
|
756
1911
|
const layout = this.functionValueLayout(
|
|
757
1912
|
param.ty,
|
|
758
1913
|
nextIndex,
|
|
759
1914
|
`parameter '${param.name}'`,
|
|
760
1915
|
false,
|
|
761
|
-
|
|
1916
|
+
this.parameterPassingMode(id, parameterId),
|
|
762
1917
|
);
|
|
763
1918
|
nextIndex += layout.components.length;
|
|
764
1919
|
return layout;
|
|
@@ -785,6 +1940,11 @@ class MirCompiler {
|
|
|
785
1940
|
const callResultLocals = this.collectCallResultLocals(func);
|
|
786
1941
|
const sliceScratch = this.collectSliceScratchLocals(func);
|
|
787
1942
|
const processFrameLocals = this.collectProcessFrameLocals(func);
|
|
1943
|
+
const generatedLocalBase =
|
|
1944
|
+
paramScalars.length +
|
|
1945
|
+
flatLocalScalars.length +
|
|
1946
|
+
callResultLocals.length +
|
|
1947
|
+
sliceScratch.count;
|
|
788
1948
|
if (
|
|
789
1949
|
resultScalars.length > 1
|
|
790
1950
|
|| callResultLocals.some((entry) => entry.resultCount > 1)
|
|
@@ -825,10 +1985,21 @@ class MirCompiler {
|
|
|
825
1985
|
),
|
|
826
1986
|
eventId: func.kind?.kind === "event" ? func.kind.data : null,
|
|
827
1987
|
processFrameLocals,
|
|
1988
|
+
generatedLocalBase,
|
|
1989
|
+
generatedLocals: [],
|
|
1990
|
+
entryInitializers: [],
|
|
1991
|
+
bufferDescriptorCache: new Map(),
|
|
1992
|
+
audioChannelPointerCache: new Map(),
|
|
828
1993
|
breakLabels: [],
|
|
829
1994
|
continueLabels: [],
|
|
830
1995
|
};
|
|
831
|
-
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
|
+
]);
|
|
832
2003
|
const functionRef = this.module.addFunction(
|
|
833
2004
|
this.functionNames[id],
|
|
834
2005
|
binaryen.createType(paramScalars.map((type) => this.wasmType(type))),
|
|
@@ -837,6 +2008,7 @@ class MirCompiler {
|
|
|
837
2008
|
...flatLocalScalars.map((type) => this.wasmType(type)),
|
|
838
2009
|
...callResultLocals.map((entry) => entry.type),
|
|
839
2010
|
...Array.from({ length: sliceScratch.count }, () => binaryen.i32),
|
|
2011
|
+
...context.generatedLocals.map((entry) => this.wasmType(entry.scalar)),
|
|
840
2012
|
],
|
|
841
2013
|
body,
|
|
842
2014
|
);
|
|
@@ -861,6 +2033,9 @@ class MirCompiler {
|
|
|
861
2033
|
);
|
|
862
2034
|
}
|
|
863
2035
|
}
|
|
2036
|
+
for (const local of context.generatedLocals) {
|
|
2037
|
+
binaryen.Function.setLocalName(functionRef, local.index, local.name);
|
|
2038
|
+
}
|
|
864
2039
|
}
|
|
865
2040
|
|
|
866
2041
|
functionValueLayout(
|
|
@@ -888,15 +2063,28 @@ class MirCompiler {
|
|
|
888
2063
|
index,
|
|
889
2064
|
typeId,
|
|
890
2065
|
kind: "slice",
|
|
891
|
-
components: ["i32", "i32", "i32"],
|
|
2066
|
+
components: ["i32", "i32", "i32", "i32"],
|
|
892
2067
|
};
|
|
893
2068
|
}
|
|
894
2069
|
if (type.kind === "buffer") {
|
|
2070
|
+
this.bufferChannelMetadata(type.data.channels, type.data.element);
|
|
895
2071
|
return {
|
|
896
2072
|
index,
|
|
897
2073
|
typeId,
|
|
898
2074
|
kind: "buffer",
|
|
899
|
-
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"],
|
|
900
2088
|
};
|
|
901
2089
|
}
|
|
902
2090
|
if (type.kind === "array") {
|
|
@@ -925,8 +2113,10 @@ class MirCompiler {
|
|
|
925
2113
|
}
|
|
926
2114
|
if (layout.kind === "array") return;
|
|
927
2115
|
const suffixes = layout.kind === "buffer"
|
|
928
|
-
? ["
|
|
929
|
-
:
|
|
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"];
|
|
930
2120
|
for (const [offset, suffix] of suffixes.entries()) {
|
|
931
2121
|
binaryen.Function.setLocalName(
|
|
932
2122
|
functionRef,
|
|
@@ -946,7 +2136,7 @@ class MirCompiler {
|
|
|
946
2136
|
this.requireFunctionId(data.function, "call target");
|
|
947
2137
|
const target = this.mir.functions[data.function];
|
|
948
2138
|
const aliasesResult = data.args.some((argument, index) =>
|
|
949
|
-
|
|
2139
|
+
this.parameterPassingMode(data.function, index) !== "value"
|
|
950
2140
|
&& argument.kind === "place"
|
|
951
2141
|
&& argument.data.base.kind === "local"
|
|
952
2142
|
&& argument.data.projections.length === 0
|
|
@@ -1049,6 +2239,62 @@ class MirCompiler {
|
|
|
1049
2239
|
);
|
|
1050
2240
|
}
|
|
1051
2241
|
|
|
2242
|
+
defaultFunctionResult(context) {
|
|
2243
|
+
const scalars = context.function.results.map((typeId, resultId) =>
|
|
2244
|
+
this.requireScalarType(
|
|
2245
|
+
typeId,
|
|
2246
|
+
`result ${resultId} of '${context.function.name}'`,
|
|
2247
|
+
),
|
|
2248
|
+
);
|
|
2249
|
+
const values = scalars.map((scalar) => this.zero(scalar));
|
|
2250
|
+
if (values.length === 0) return undefined;
|
|
2251
|
+
if (values.length === 1) return values[0];
|
|
2252
|
+
return this.module.tuple.make(values);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
returnFromCurrentFunction(context) {
|
|
2256
|
+
return this.module.return(this.defaultFunctionResult(context));
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
raiseRuntimeFailure(context) {
|
|
2260
|
+
return this.module.block(null, [
|
|
2261
|
+
this.module.global.set(
|
|
2262
|
+
RUNTIME_FAILURE_GLOBAL,
|
|
2263
|
+
this.module.i32.const(PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE),
|
|
2264
|
+
),
|
|
2265
|
+
this.returnFromCurrentFunction(context),
|
|
2266
|
+
]);
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
propagateRuntimeFailure(calleeId, context) {
|
|
2270
|
+
if (!this.functionMayFail[calleeId]) return [];
|
|
2271
|
+
return [
|
|
2272
|
+
this.module.if(
|
|
2273
|
+
this.module.i32.ne(
|
|
2274
|
+
this.module.global.get(RUNTIME_FAILURE_GLOBAL, binaryen.i32),
|
|
2275
|
+
this.module.i32.const(PROCESSOR_EXECUTION_OK),
|
|
2276
|
+
),
|
|
2277
|
+
this.returnFromCurrentFunction(context),
|
|
2278
|
+
),
|
|
2279
|
+
];
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
resetRuntimeFailure(functionId) {
|
|
2283
|
+
if (!this.functionMayFail[functionId]) return [];
|
|
2284
|
+
return [
|
|
2285
|
+
this.module.global.set(
|
|
2286
|
+
RUNTIME_FAILURE_GLOBAL,
|
|
2287
|
+
this.module.i32.const(PROCESSOR_EXECUTION_OK),
|
|
2288
|
+
),
|
|
2289
|
+
];
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
executionStatus(functionId) {
|
|
2293
|
+
return this.functionMayFail[functionId]
|
|
2294
|
+
? this.module.global.get(RUNTIME_FAILURE_GLOBAL, binaryen.i32)
|
|
2295
|
+
: this.module.i32.const(PROCESSOR_EXECUTION_OK);
|
|
2296
|
+
}
|
|
2297
|
+
|
|
1052
2298
|
addAbiWrappers() {
|
|
1053
2299
|
const initId = this.mir.entry_points.init;
|
|
1054
2300
|
const processId = this.mir.entry_points.process;
|
|
@@ -1062,6 +2308,7 @@ class MirCompiler {
|
|
|
1062
2308
|
(this.options.simd ? binaryen.Features.SIMD128 : 0),
|
|
1063
2309
|
);
|
|
1064
2310
|
const initBody = this.module.block(null, [
|
|
2311
|
+
...this.resetRuntimeFailure(initId),
|
|
1065
2312
|
this.module.global.set(
|
|
1066
2313
|
POINTER_GLOBALS.params,
|
|
1067
2314
|
this.module.local.get(0, binaryen.i32),
|
|
@@ -1076,11 +2323,12 @@ class MirCompiler {
|
|
|
1076
2323
|
this.module.i32.const(this.stateLayout.byteLength ?? 0),
|
|
1077
2324
|
),
|
|
1078
2325
|
this.module.call(this.functionNames[initId], [], binaryen.none),
|
|
1079
|
-
|
|
2326
|
+
this.executionStatus(initId),
|
|
2327
|
+
], binaryen.i32);
|
|
1080
2328
|
this.module.addFunction(
|
|
1081
2329
|
"$onda.abi.init",
|
|
1082
2330
|
binaryen.createType([binaryen.i32, binaryen.i32]),
|
|
1083
|
-
binaryen.
|
|
2331
|
+
binaryen.i32,
|
|
1084
2332
|
[],
|
|
1085
2333
|
initBody,
|
|
1086
2334
|
);
|
|
@@ -1121,7 +2369,13 @@ class MirCompiler {
|
|
|
1121
2369
|
),
|
|
1122
2370
|
);
|
|
1123
2371
|
const processBody = this.module.block(null, [
|
|
1124
|
-
this.module.if(
|
|
2372
|
+
this.module.if(
|
|
2373
|
+
invalidRange,
|
|
2374
|
+
this.module.return(
|
|
2375
|
+
this.module.i32.const(PROCESSOR_EXECUTION_RUNTIME_SAFETY_FAILURE),
|
|
2376
|
+
),
|
|
2377
|
+
),
|
|
2378
|
+
...this.resetRuntimeFailure(processId),
|
|
1125
2379
|
this.module.global.set(
|
|
1126
2380
|
POINTER_GLOBALS.state,
|
|
1127
2381
|
this.module.local.get(0, binaryen.i32),
|
|
@@ -1142,6 +2396,10 @@ class MirCompiler {
|
|
|
1142
2396
|
POINTER_GLOBALS.buffers,
|
|
1143
2397
|
this.module.local.get(7, binaryen.i32),
|
|
1144
2398
|
),
|
|
2399
|
+
this.module.global.set(
|
|
2400
|
+
POINTER_GLOBALS.bufferWrites,
|
|
2401
|
+
this.module.local.get(7, binaryen.i32),
|
|
2402
|
+
),
|
|
1145
2403
|
this.module.global.set(
|
|
1146
2404
|
POINTER_GLOBALS.bufferFrames,
|
|
1147
2405
|
this.module.local.get(8, binaryen.i32),
|
|
@@ -1159,11 +2417,12 @@ class MirCompiler {
|
|
|
1159
2417
|
[startFrame(), frames(), flags()],
|
|
1160
2418
|
binaryen.none,
|
|
1161
2419
|
),
|
|
1162
|
-
|
|
2420
|
+
this.executionStatus(processId),
|
|
2421
|
+
], binaryen.i32);
|
|
1163
2422
|
this.module.addFunction(
|
|
1164
2423
|
"$onda.abi.process",
|
|
1165
2424
|
processParams,
|
|
1166
|
-
binaryen.
|
|
2425
|
+
binaryen.i32,
|
|
1167
2426
|
[],
|
|
1168
2427
|
processBody,
|
|
1169
2428
|
);
|
|
@@ -1182,6 +2441,7 @@ class MirCompiler {
|
|
|
1182
2441
|
}
|
|
1183
2442
|
const wrapperName = `$onda.abi.event.${eventId}`;
|
|
1184
2443
|
const body = this.module.block(null, [
|
|
2444
|
+
...this.resetRuntimeFailure(event.handler),
|
|
1185
2445
|
this.module.global.set(
|
|
1186
2446
|
POINTER_GLOBALS.eventPayload,
|
|
1187
2447
|
this.module.local.get(0, binaryen.i32),
|
|
@@ -1198,6 +2458,10 @@ class MirCompiler {
|
|
|
1198
2458
|
POINTER_GLOBALS.buffers,
|
|
1199
2459
|
this.module.local.get(3, binaryen.i32),
|
|
1200
2460
|
),
|
|
2461
|
+
this.module.global.set(
|
|
2462
|
+
POINTER_GLOBALS.bufferWrites,
|
|
2463
|
+
this.module.local.get(3, binaryen.i32),
|
|
2464
|
+
),
|
|
1201
2465
|
this.module.global.set(
|
|
1202
2466
|
POINTER_GLOBALS.bufferFrames,
|
|
1203
2467
|
this.module.local.get(4, binaryen.i32),
|
|
@@ -1211,11 +2475,12 @@ class MirCompiler {
|
|
|
1211
2475
|
this.module.local.get(6, binaryen.i32),
|
|
1212
2476
|
),
|
|
1213
2477
|
this.module.call(this.functionNames[event.handler], [], binaryen.none),
|
|
1214
|
-
|
|
2478
|
+
this.executionStatus(event.handler),
|
|
2479
|
+
], binaryen.i32);
|
|
1215
2480
|
this.module.addFunction(
|
|
1216
2481
|
wrapperName,
|
|
1217
2482
|
binaryen.createType(Array.from({ length: 7 }, () => binaryen.i32)),
|
|
1218
|
-
binaryen.
|
|
2483
|
+
binaryen.i32,
|
|
1219
2484
|
[],
|
|
1220
2485
|
body,
|
|
1221
2486
|
);
|
|
@@ -1326,7 +2591,16 @@ class MirCompiler {
|
|
|
1326
2591
|
const args = data.args.flatMap((argument, index) => {
|
|
1327
2592
|
const parameterType = this.type(target.params[index].ty);
|
|
1328
2593
|
if (parameterType.kind === "scalar") {
|
|
1329
|
-
|
|
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
|
+
}
|
|
1330
2604
|
if (argument.kind !== "value") {
|
|
1331
2605
|
this.fail(`scalar call argument ${index} of '${target.name}' is not a value`);
|
|
1332
2606
|
}
|
|
@@ -1345,6 +2619,7 @@ class MirCompiler {
|
|
|
1345
2619
|
argument.data.index,
|
|
1346
2620
|
argument.data.bounds,
|
|
1347
2621
|
context,
|
|
2622
|
+
target.params[index].mode === "read_write_reference",
|
|
1348
2623
|
),
|
|
1349
2624
|
];
|
|
1350
2625
|
}
|
|
@@ -1374,22 +2649,32 @@ class MirCompiler {
|
|
|
1374
2649
|
];
|
|
1375
2650
|
}
|
|
1376
2651
|
return [
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
2652
|
+
this.compileSliceWindowAddress(
|
|
2653
|
+
argument.data,
|
|
2654
|
+
parameterType,
|
|
2655
|
+
context,
|
|
2656
|
+
target.params[index].mode === "read_write_reference",
|
|
2657
|
+
),
|
|
1382
2658
|
];
|
|
1383
2659
|
}
|
|
1384
2660
|
if (parameterType.kind === "buffer") {
|
|
1385
2661
|
if (argument.kind === "buffer") {
|
|
1386
|
-
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);
|
|
1387
2666
|
}
|
|
1388
2667
|
if (argument.kind === "place") {
|
|
1389
2668
|
return this.loadBufferPlace(argument.data, context);
|
|
1390
2669
|
}
|
|
1391
2670
|
this.fail(`buffer call argument ${index} of '${target.name}' is invalid`);
|
|
1392
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
|
+
}
|
|
1393
2678
|
this.fail(
|
|
1394
2679
|
`call argument ${index} of '${target.name}' has unsupported type '${parameterType.kind}'`,
|
|
1395
2680
|
);
|
|
@@ -1405,7 +2690,7 @@ class MirCompiler {
|
|
|
1405
2690
|
const localReferenceSync = data.args.flatMap((argument, index) => {
|
|
1406
2691
|
const parameter = target.params[index];
|
|
1407
2692
|
if (
|
|
1408
|
-
|
|
2693
|
+
this.parameterPassingMode(data.function, index) === "value"
|
|
1409
2694
|
|| argument.kind !== "place"
|
|
1410
2695
|
|| argument.data.base.kind !== "local"
|
|
1411
2696
|
|| argument.data.projections.length !== 0
|
|
@@ -1465,6 +2750,7 @@ class MirCompiler {
|
|
|
1465
2750
|
this.module.local.set(resultSpill.index, call),
|
|
1466
2751
|
...afterCall,
|
|
1467
2752
|
...assignResults,
|
|
2753
|
+
...this.propagateRuntimeFailure(data.function, context),
|
|
1468
2754
|
]);
|
|
1469
2755
|
}
|
|
1470
2756
|
let compiledCall;
|
|
@@ -1492,8 +2778,18 @@ class MirCompiler {
|
|
|
1492
2778
|
),
|
|
1493
2779
|
]);
|
|
1494
2780
|
}
|
|
1495
|
-
if (localReferenceSync.length === 0)
|
|
1496
|
-
|
|
2781
|
+
if (localReferenceSync.length === 0) {
|
|
2782
|
+
const propagation = this.propagateRuntimeFailure(data.function, context);
|
|
2783
|
+
return propagation.length === 0
|
|
2784
|
+
? compiledCall
|
|
2785
|
+
: this.module.block(null, [compiledCall, ...propagation]);
|
|
2786
|
+
}
|
|
2787
|
+
return this.module.block(null, [
|
|
2788
|
+
...beforeCall,
|
|
2789
|
+
compiledCall,
|
|
2790
|
+
...afterCall,
|
|
2791
|
+
...this.propagateRuntimeFailure(data.function, context),
|
|
2792
|
+
]);
|
|
1497
2793
|
}
|
|
1498
2794
|
|
|
1499
2795
|
compileOutputStore(data, context) {
|
|
@@ -1502,12 +2798,13 @@ class MirCompiler {
|
|
|
1502
2798
|
if (!port) {
|
|
1503
2799
|
this.fail(`output id ${data.output} is out of range`);
|
|
1504
2800
|
}
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
2801
|
+
const channelPointer = this.audioChannelPointer(
|
|
2802
|
+
POINTER_GLOBALS.outputs,
|
|
2803
|
+
port,
|
|
2804
|
+
data.element,
|
|
2805
|
+
data.bounds,
|
|
2806
|
+
context,
|
|
1509
2807
|
);
|
|
1510
|
-
const channelPointer = this.module.i32.load(0, 4, tableAddress);
|
|
1511
2808
|
const sampleAddress = this.module.i32.add(
|
|
1512
2809
|
channelPointer,
|
|
1513
2810
|
this.module.i32.mul(
|
|
@@ -1564,13 +2861,13 @@ class MirCompiler {
|
|
|
1564
2861
|
}
|
|
1565
2862
|
|
|
1566
2863
|
compileBufferStore(data, context) {
|
|
1567
|
-
const buffer = this.
|
|
2864
|
+
const buffer = this.requireBufferRef(data.buffer);
|
|
1568
2865
|
if (buffer.access !== "read_write") {
|
|
1569
2866
|
this.fail(`buffer '${buffer.name}' is read-only`);
|
|
1570
2867
|
}
|
|
1571
2868
|
return this.storeScalar(
|
|
1572
2869
|
buffer.element,
|
|
1573
|
-
this.compileBufferAddress(data, context),
|
|
2870
|
+
this.compileBufferAddress(data, context, true),
|
|
1574
2871
|
this.compileValue(data.value, context),
|
|
1575
2872
|
);
|
|
1576
2873
|
}
|
|
@@ -1582,7 +2879,7 @@ class MirCompiler {
|
|
|
1582
2879
|
}
|
|
1583
2880
|
return this.storeScalar(
|
|
1584
2881
|
type.data.element,
|
|
1585
|
-
this.compileBufferParamAddress(data, context),
|
|
2882
|
+
this.compileBufferParamAddress(data, context, true),
|
|
1586
2883
|
this.compileValue(data.value, context),
|
|
1587
2884
|
);
|
|
1588
2885
|
}
|
|
@@ -1608,6 +2905,7 @@ class MirCompiler {
|
|
|
1608
2905
|
scalar,
|
|
1609
2906
|
() => this.compileValue(data.lhs, context),
|
|
1610
2907
|
() => this.compileValue(data.rhs, context),
|
|
2908
|
+
context,
|
|
1611
2909
|
);
|
|
1612
2910
|
}
|
|
1613
2911
|
case "compare": {
|
|
@@ -1640,27 +2938,24 @@ class MirCompiler {
|
|
|
1640
2938
|
case "buffer_param_load":
|
|
1641
2939
|
return this.compileBufferParamLoad(data, context);
|
|
1642
2940
|
case "buffer_len":
|
|
1643
|
-
return this.compileBufferLen(data);
|
|
2941
|
+
return this.compileBufferLen(data, context);
|
|
1644
2942
|
case "buffer_param_len":
|
|
1645
2943
|
return this.compileBufferParamLen(data, context);
|
|
1646
2944
|
case "buffer_channels":
|
|
1647
|
-
return this.
|
|
1648
|
-
POINTER_GLOBALS.bufferChannels,
|
|
1649
|
-
data,
|
|
1650
|
-
"i32",
|
|
1651
|
-
);
|
|
2945
|
+
return this.compileBufferChannels(data, context);
|
|
1652
2946
|
case "buffer_param_channels":
|
|
1653
|
-
return this.
|
|
2947
|
+
return this.compileBufferParamChannels(data, context);
|
|
1654
2948
|
case "buffer_sample_rate":
|
|
1655
2949
|
return this.loadBufferTableValue(
|
|
1656
2950
|
POINTER_GLOBALS.bufferSampleRates,
|
|
1657
2951
|
data,
|
|
1658
2952
|
"f32",
|
|
2953
|
+
context,
|
|
1659
2954
|
);
|
|
1660
2955
|
case "buffer_param_sample_rate":
|
|
1661
|
-
return this.loadBufferParamComponent(data,
|
|
2956
|
+
return this.loadBufferParamComponent(data, 4, "f32", context);
|
|
1662
2957
|
case "slice_len":
|
|
1663
|
-
return this.compileSliceValue(data, context)[
|
|
2958
|
+
return this.compileSliceValue(data, context)[2];
|
|
1664
2959
|
case "slice_load":
|
|
1665
2960
|
return this.compileSliceLoad(data, context);
|
|
1666
2961
|
case "make_slice":
|
|
@@ -1690,12 +2985,13 @@ class MirCompiler {
|
|
|
1690
2985
|
if (!port) {
|
|
1691
2986
|
this.fail(`input id ${data.input} is out of range`);
|
|
1692
2987
|
}
|
|
1693
|
-
const
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
2988
|
+
const channelPointer = this.audioChannelPointer(
|
|
2989
|
+
POINTER_GLOBALS.inputs,
|
|
2990
|
+
port,
|
|
2991
|
+
data.element,
|
|
2992
|
+
data.bounds,
|
|
2993
|
+
context,
|
|
1697
2994
|
);
|
|
1698
|
-
const channelPointer = this.module.i32.load(0, 4, tableAddress);
|
|
1699
2995
|
const sampleAddress = this.module.i32.add(
|
|
1700
2996
|
channelPointer,
|
|
1701
2997
|
this.module.i32.mul(
|
|
@@ -1718,7 +3014,7 @@ class MirCompiler {
|
|
|
1718
3014
|
const invalid = this.module.i32.ge_u(offset(), frames());
|
|
1719
3015
|
return this.module.if(
|
|
1720
3016
|
invalid,
|
|
1721
|
-
this.
|
|
3017
|
+
this.raiseRuntimeFailure(context),
|
|
1722
3018
|
this.module.i32.add(startFrame(), offset()),
|
|
1723
3019
|
);
|
|
1724
3020
|
}
|
|
@@ -1739,12 +3035,13 @@ class MirCompiler {
|
|
|
1739
3035
|
if (!port) {
|
|
1740
3036
|
this.fail(`output id ${data.output} is out of range`);
|
|
1741
3037
|
}
|
|
1742
|
-
const
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
3038
|
+
const channelPointer = this.audioChannelPointer(
|
|
3039
|
+
POINTER_GLOBALS.outputs,
|
|
3040
|
+
port,
|
|
3041
|
+
data.element,
|
|
3042
|
+
data.bounds,
|
|
3043
|
+
context,
|
|
1746
3044
|
);
|
|
1747
|
-
const channelPointer = this.module.i32.load(0, 4, tableAddress);
|
|
1748
3045
|
const sampleAddress = this.module.i32.add(
|
|
1749
3046
|
channelPointer,
|
|
1750
3047
|
this.module.i32.mul(
|
|
@@ -1769,6 +3066,62 @@ class MirCompiler {
|
|
|
1769
3066
|
return this.module.i32.add(this.module.i32.const(port.channel), index);
|
|
1770
3067
|
}
|
|
1771
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
|
+
|
|
1772
3125
|
compileConstDataLoad(data, context) {
|
|
1773
3126
|
const item = this.constLayout[data.data];
|
|
1774
3127
|
if (!item) {
|
|
@@ -1783,10 +3136,10 @@ class MirCompiler {
|
|
|
1783
3136
|
}
|
|
1784
3137
|
|
|
1785
3138
|
compileBufferLoad(data, context) {
|
|
1786
|
-
const buffer = this.
|
|
3139
|
+
const buffer = this.requireBufferRef(data.buffer);
|
|
1787
3140
|
return this.loadScalar(
|
|
1788
3141
|
buffer.element,
|
|
1789
|
-
this.compileBufferAddress(data, context),
|
|
3142
|
+
this.compileBufferAddress(data, context, false),
|
|
1790
3143
|
);
|
|
1791
3144
|
}
|
|
1792
3145
|
|
|
@@ -1794,161 +3147,798 @@ class MirCompiler {
|
|
|
1794
3147
|
const type = this.bufferParamType(data.parameter, context);
|
|
1795
3148
|
return this.loadScalar(
|
|
1796
3149
|
type.data.element,
|
|
1797
|
-
this.compileBufferParamAddress(data, context),
|
|
3150
|
+
this.compileBufferParamAddress(data, context, false),
|
|
1798
3151
|
);
|
|
1799
3152
|
}
|
|
1800
3153
|
|
|
1801
|
-
compileBufferParamAddress(data, context) {
|
|
3154
|
+
compileBufferParamAddress(data, context, write) {
|
|
1802
3155
|
const type = this.bufferParamType(data.parameter, context);
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
3156
|
+
return this.withBufferParamSelector(
|
|
3157
|
+
data.parameter,
|
|
3158
|
+
context,
|
|
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
|
+
},
|
|
1827
3233
|
);
|
|
1828
3234
|
}
|
|
1829
3235
|
|
|
1830
|
-
compileBufferAddress(data, context) {
|
|
1831
|
-
const buffer = this.
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
this.
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
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,
|
|
1842
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,
|
|
1843
3336
|
),
|
|
1844
3337
|
),
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
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,
|
|
3351
|
+
index,
|
|
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,
|
|
1852
3485
|
true,
|
|
1853
3486
|
);
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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,
|
|
1857
3503
|
"i32",
|
|
3504
|
+
"buffer_param.selector",
|
|
1858
3505
|
);
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
this.module.i32.const(this.scalarSize(buffer.element)),
|
|
3506
|
+
const prelude = [
|
|
3507
|
+
this.module.local.set(
|
|
3508
|
+
selectorLocal,
|
|
3509
|
+
this.compileBufferParamSelector(parameterRef, context),
|
|
1864
3510
|
),
|
|
3511
|
+
];
|
|
3512
|
+
const result = build(
|
|
3513
|
+
() => this.module.local.get(selectorLocal, binaryen.i32),
|
|
3514
|
+
null,
|
|
3515
|
+
prelude,
|
|
1865
3516
|
);
|
|
3517
|
+
return this.module.block(null, [...prelude, result], resultType);
|
|
1866
3518
|
}
|
|
1867
3519
|
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
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,
|
|
3538
|
+
this.module.i32.mul(
|
|
3539
|
+
index,
|
|
3540
|
+
this.module.i32.const(this.scalarSize(scalar)),
|
|
3541
|
+
),
|
|
3542
|
+
);
|
|
3543
|
+
return this.loadScalar(scalar, address);
|
|
3544
|
+
};
|
|
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,
|
|
3586
|
+
);
|
|
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,
|
|
1873
3594
|
"i32",
|
|
3595
|
+
selector,
|
|
3596
|
+
staticSelector,
|
|
3597
|
+
context,
|
|
1874
3598
|
);
|
|
1875
3599
|
}
|
|
1876
3600
|
|
|
1877
|
-
|
|
1878
|
-
this.
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
this.
|
|
1882
|
-
|
|
1883
|
-
|
|
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,
|
|
1884
3630
|
"i32",
|
|
1885
|
-
|
|
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)),
|
|
1886
3668
|
);
|
|
1887
3669
|
}
|
|
1888
3670
|
|
|
1889
|
-
|
|
1890
|
-
this.
|
|
1891
|
-
|
|
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
|
+
));
|
|
1892
3770
|
}
|
|
1893
3771
|
|
|
1894
|
-
|
|
1895
|
-
this.
|
|
1896
|
-
return this.
|
|
1897
|
-
|
|
1898
|
-
|
|
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,
|
|
3780
|
+
index,
|
|
3781
|
+
staticIndex,
|
|
3782
|
+
scalar,
|
|
3783
|
+
context,
|
|
3784
|
+
)(),
|
|
1899
3785
|
);
|
|
1900
3786
|
}
|
|
1901
3787
|
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
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;
|
|
1907
3816
|
}
|
|
1908
|
-
|
|
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));
|
|
1909
3834
|
}
|
|
1910
3835
|
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
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);
|
|
1915
3845
|
}
|
|
1916
|
-
|
|
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,
|
|
3853
|
+
"i32",
|
|
3854
|
+
context,
|
|
3855
|
+
);
|
|
1917
3856
|
}
|
|
1918
3857
|
|
|
1919
|
-
|
|
1920
|
-
const
|
|
1921
|
-
|
|
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
|
+
),
|
|
3868
|
+
),
|
|
3869
|
+
);
|
|
3870
|
+
return load();
|
|
1922
3871
|
}
|
|
1923
3872
|
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
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,
|
|
1931
3895
|
);
|
|
1932
3896
|
}
|
|
1933
3897
|
|
|
1934
|
-
|
|
1935
|
-
this.
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
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
|
+
);
|
|
3906
|
+
}
|
|
3907
|
+
const indexLocal = this.allocateGeneratedLocal(
|
|
3908
|
+
context,
|
|
3909
|
+
"i32",
|
|
3910
|
+
"buffer.descriptor_index",
|
|
3911
|
+
);
|
|
3912
|
+
const prelude = [
|
|
3913
|
+
this.module.local.set(
|
|
3914
|
+
indexLocal,
|
|
3915
|
+
this.compileBufferRefIndex(bufferRef, context),
|
|
3916
|
+
),
|
|
1941
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);
|
|
1942
3924
|
}
|
|
1943
3925
|
|
|
1944
|
-
|
|
1945
|
-
this.
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
this.module.global.get(globalName, binaryen.i32),
|
|
1949
|
-
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()),
|
|
1950
3930
|
);
|
|
1951
|
-
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;
|
|
1952
3942
|
}
|
|
1953
3943
|
|
|
1954
3944
|
compileSliceValue(value, context) {
|
|
@@ -1982,8 +3972,11 @@ class MirCompiler {
|
|
|
1982
3972
|
}
|
|
1983
3973
|
const header = () =>
|
|
1984
3974
|
this.compileEventParamAddress(context.eventId, place.base.data);
|
|
3975
|
+
const address = () =>
|
|
3976
|
+
this.module.i32.add(header(), this.module.i32.const(4));
|
|
1985
3977
|
return [
|
|
1986
|
-
|
|
3978
|
+
address(),
|
|
3979
|
+
address(),
|
|
1987
3980
|
this.module.i32.load(0, 4, header()),
|
|
1988
3981
|
this.module.i32.const(this.scalarSize(type.data.element)),
|
|
1989
3982
|
];
|
|
@@ -2043,7 +4036,7 @@ class MirCompiler {
|
|
|
2043
4036
|
this.fail("slice assignment destination must be an unprojected local");
|
|
2044
4037
|
}
|
|
2045
4038
|
const layout = context.localLayouts[place.base.data];
|
|
2046
|
-
if (!layout || layout.kind !== "slice" || components.length !==
|
|
4039
|
+
if (!layout || layout.kind !== "slice" || components.length !== 4) {
|
|
2047
4040
|
this.fail(`local id ${place.base.data} is not a valid slice destination`);
|
|
2048
4041
|
}
|
|
2049
4042
|
return this.module.block(
|
|
@@ -2055,27 +4048,53 @@ class MirCompiler {
|
|
|
2055
4048
|
}
|
|
2056
4049
|
|
|
2057
4050
|
compileMakeSlice(data, context) {
|
|
2058
|
-
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);
|
|
2059
4064
|
const range = this.compileSliceRange(
|
|
2060
4065
|
() => this.compileValue(data.start, context),
|
|
2061
4066
|
() => this.compileValue(data.len, context),
|
|
2062
|
-
|
|
4067
|
+
source(2),
|
|
2063
4068
|
data.bounds,
|
|
4069
|
+
context,
|
|
2064
4070
|
);
|
|
2065
|
-
|
|
4071
|
+
const result = [
|
|
2066
4072
|
this.module.i32.add(
|
|
2067
|
-
source()
|
|
4073
|
+
source(0)(),
|
|
2068
4074
|
this.module.i32.mul(
|
|
2069
4075
|
range.start(),
|
|
2070
|
-
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)(),
|
|
2071
4084
|
),
|
|
2072
4085
|
),
|
|
2073
4086
|
range.len(),
|
|
2074
|
-
source()
|
|
4087
|
+
source(3)(),
|
|
2075
4088
|
];
|
|
4089
|
+
result[0] = this.module.block(
|
|
4090
|
+
null,
|
|
4091
|
+
[...initializeSource, result[0]],
|
|
4092
|
+
binaryen.i32,
|
|
4093
|
+
);
|
|
4094
|
+
return result;
|
|
2076
4095
|
}
|
|
2077
4096
|
|
|
2078
|
-
compileSliceRange(start, len, sourceLen, bounds) {
|
|
4097
|
+
compileSliceRange(start, len, sourceLen, bounds, context) {
|
|
2079
4098
|
const zero = () => this.module.i32.const(0);
|
|
2080
4099
|
if (bounds === "unchecked") {
|
|
2081
4100
|
return { start, len };
|
|
@@ -2111,7 +4130,7 @@ class MirCompiler {
|
|
|
2111
4130
|
};
|
|
2112
4131
|
return { start: normalizedStart, len: normalizedLen };
|
|
2113
4132
|
}
|
|
2114
|
-
if (bounds === "
|
|
4133
|
+
if (bounds === "checked") {
|
|
2115
4134
|
const invalid = () => {
|
|
2116
4135
|
const remaining = () => this.module.i32.sub(sourceLen(), start());
|
|
2117
4136
|
return this.module.i32.or(
|
|
@@ -2127,7 +4146,7 @@ class MirCompiler {
|
|
|
2127
4146
|
};
|
|
2128
4147
|
return {
|
|
2129
4148
|
start: () =>
|
|
2130
|
-
this.module.if(invalid(), this.
|
|
4149
|
+
this.module.if(invalid(), this.raiseRuntimeFailure(context), start()),
|
|
2131
4150
|
len,
|
|
2132
4151
|
};
|
|
2133
4152
|
}
|
|
@@ -2149,6 +4168,7 @@ class MirCompiler {
|
|
|
2149
4168
|
this.fail("slice array source must have primitive elements");
|
|
2150
4169
|
}
|
|
2151
4170
|
return [
|
|
4171
|
+
this.placeAddress(source.data, context),
|
|
2152
4172
|
this.placeAddress(source.data, context),
|
|
2153
4173
|
this.module.i32.const(type.data.len),
|
|
2154
4174
|
this.module.i32.const(this.scalarSize(element.data)),
|
|
@@ -2158,79 +4178,229 @@ class MirCompiler {
|
|
|
2158
4178
|
const item = this.constLayout[source.data];
|
|
2159
4179
|
if (!item) this.fail(`const data id ${source.data} is out of range`);
|
|
2160
4180
|
return [
|
|
4181
|
+
this.module.i32.const(item.address),
|
|
2161
4182
|
this.module.i32.const(item.address),
|
|
2162
4183
|
this.module.i32.const(item.len),
|
|
2163
4184
|
this.module.i32.const(this.scalarSize(item.scalar)),
|
|
2164
4185
|
];
|
|
2165
4186
|
}
|
|
2166
4187
|
if (source.kind === "buffer") {
|
|
2167
|
-
const buffer = this.
|
|
4188
|
+
const buffer = this.requireBufferRef(source.data.buffer);
|
|
2168
4189
|
const elementSize = this.scalarSize(buffer.element);
|
|
2169
|
-
const
|
|
2170
|
-
|
|
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,
|
|
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
|
+
),
|
|
4204
|
+
);
|
|
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,
|
|
4215
|
+
context,
|
|
4216
|
+
);
|
|
4217
|
+
let channels = this.bufferChannelsFactory(
|
|
2171
4218
|
source.data.buffer,
|
|
2172
|
-
|
|
4219
|
+
descriptorIndex,
|
|
4220
|
+
staticIndex,
|
|
4221
|
+
context,
|
|
2173
4222
|
);
|
|
2174
|
-
if (
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
}
|
|
2181
|
-
const channels = () =>
|
|
2182
|
-
this.loadBufferTableValue(
|
|
2183
|
-
POINTER_GLOBALS.bufferChannels,
|
|
2184
|
-
source.data.buffer,
|
|
4223
|
+
if (
|
|
4224
|
+
staticIndex === null
|
|
4225
|
+
&& this.bufferRefChannelMetadata(source.data.buffer).kind === "dynamic"
|
|
4226
|
+
) {
|
|
4227
|
+
channels = this.snapshotOperationValue(
|
|
4228
|
+
channels,
|
|
2185
4229
|
"i32",
|
|
4230
|
+
"buffer.channels",
|
|
4231
|
+
prelude,
|
|
4232
|
+
context,
|
|
2186
4233
|
);
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
)
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
address,
|
|
2196
|
-
this.module.i32.mul(channel, this.module.i32.const(elementSize)),
|
|
2197
|
-
),
|
|
2198
|
-
this.loadBufferTableValue(
|
|
2199
|
-
POINTER_GLOBALS.bufferFrames,
|
|
2200
|
-
source.data.buffer,
|
|
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,
|
|
2201
4242
|
"i32",
|
|
2202
|
-
|
|
2203
|
-
|
|
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)),
|
|
2204
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;
|
|
2205
4290
|
}
|
|
2206
4291
|
if (source.kind === "buffer_param") {
|
|
2207
4292
|
const type = this.bufferParamType(source.data.parameter, context);
|
|
2208
4293
|
const elementSize = this.scalarSize(type.data.element);
|
|
2209
|
-
const
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
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);
|
|
2217
4318
|
}
|
|
2218
|
-
const
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
4319
|
+
const component = (offset, scalar) => this.bufferParamComponentFactory(
|
|
4320
|
+
source.data.parameter,
|
|
4321
|
+
offset,
|
|
4322
|
+
scalar,
|
|
4323
|
+
selector,
|
|
4324
|
+
staticSelector,
|
|
4325
|
+
context,
|
|
2225
4326
|
);
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
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)),
|
|
2233
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;
|
|
2234
4404
|
}
|
|
2235
4405
|
this.fail(`unsupported slice source '${String(source.kind)}'`);
|
|
2236
4406
|
}
|
|
@@ -2255,23 +4425,26 @@ class MirCompiler {
|
|
|
2255
4425
|
return type.data.access;
|
|
2256
4426
|
}
|
|
2257
4427
|
|
|
2258
|
-
compileSliceAddress(slice, index, bounds, context) {
|
|
4428
|
+
compileSliceAddress(slice, index, bounds, context, write) {
|
|
2259
4429
|
return this.compileSliceAddressWithFactories(
|
|
2260
4430
|
() => this.compileSliceValue(slice, context),
|
|
2261
4431
|
() => this.compileValue(index, context),
|
|
2262
4432
|
bounds,
|
|
4433
|
+
context,
|
|
4434
|
+
write,
|
|
2263
4435
|
);
|
|
2264
4436
|
}
|
|
2265
4437
|
|
|
2266
|
-
compileSliceAddressWithFactories(slice, index, bounds) {
|
|
4438
|
+
compileSliceAddressWithFactories(slice, index, bounds, context, write) {
|
|
2267
4439
|
const bounded = this.compileDynamicBoundedIndex(
|
|
2268
4440
|
index,
|
|
2269
|
-
() => slice()[
|
|
4441
|
+
() => slice()[2],
|
|
2270
4442
|
bounds,
|
|
4443
|
+
context,
|
|
2271
4444
|
);
|
|
2272
4445
|
return this.module.i32.add(
|
|
2273
|
-
slice()[0],
|
|
2274
|
-
this.module.i32.mul(bounded, slice()[
|
|
4446
|
+
slice()[write ? 1 : 0],
|
|
4447
|
+
this.module.i32.mul(bounded, slice()[3]),
|
|
2275
4448
|
);
|
|
2276
4449
|
}
|
|
2277
4450
|
|
|
@@ -2295,6 +4468,7 @@ class MirCompiler {
|
|
|
2295
4468
|
sourceType.data.len - parameterType.data.len,
|
|
2296
4469
|
),
|
|
2297
4470
|
data.bounds,
|
|
4471
|
+
context,
|
|
2298
4472
|
);
|
|
2299
4473
|
return this.module.i32.add(
|
|
2300
4474
|
this.placeAddress(data.array, context),
|
|
@@ -2302,7 +4476,7 @@ class MirCompiler {
|
|
|
2302
4476
|
);
|
|
2303
4477
|
}
|
|
2304
4478
|
|
|
2305
|
-
compileSliceWindowAddress(data, parameterType, context) {
|
|
4479
|
+
compileSliceWindowAddress(data, parameterType, context, write) {
|
|
2306
4480
|
const elementType = this.type(parameterType.data.element);
|
|
2307
4481
|
if (elementType.kind !== "scalar") {
|
|
2308
4482
|
this.fail("slice-window fixed-array parameter element is not scalar");
|
|
@@ -2314,15 +4488,16 @@ class MirCompiler {
|
|
|
2314
4488
|
() => this.compileValue(data.start, context),
|
|
2315
4489
|
() =>
|
|
2316
4490
|
this.module.i32.sub(
|
|
2317
|
-
slice()[
|
|
4491
|
+
slice()[2],
|
|
2318
4492
|
this.module.i32.const(requiredLen),
|
|
2319
4493
|
),
|
|
2320
4494
|
data.bounds,
|
|
4495
|
+
context,
|
|
2321
4496
|
);
|
|
2322
4497
|
const address = () =>
|
|
2323
4498
|
this.module.i32.add(
|
|
2324
|
-
slice()[0],
|
|
2325
|
-
this.module.i32.mul(start(), slice()[
|
|
4499
|
+
slice()[write ? 1 : 0],
|
|
4500
|
+
this.module.i32.mul(start(), slice()[3]),
|
|
2326
4501
|
);
|
|
2327
4502
|
if (data.bounds === "unchecked") {
|
|
2328
4503
|
return address();
|
|
@@ -2330,22 +4505,22 @@ class MirCompiler {
|
|
|
2330
4505
|
const invalidShape = () =>
|
|
2331
4506
|
this.module.i32.or(
|
|
2332
4507
|
this.module.i32.ne(
|
|
2333
|
-
slice()[
|
|
4508
|
+
slice()[3],
|
|
2334
4509
|
this.module.i32.const(elementSize),
|
|
2335
4510
|
),
|
|
2336
4511
|
this.module.i32.lt_s(
|
|
2337
|
-
slice()[
|
|
4512
|
+
slice()[2],
|
|
2338
4513
|
this.module.i32.const(requiredLen),
|
|
2339
4514
|
),
|
|
2340
4515
|
);
|
|
2341
4516
|
return this.module.if(
|
|
2342
4517
|
invalidShape(),
|
|
2343
|
-
this.
|
|
4518
|
+
this.raiseRuntimeFailure(context),
|
|
2344
4519
|
address(),
|
|
2345
4520
|
);
|
|
2346
4521
|
}
|
|
2347
4522
|
|
|
2348
|
-
compileWindowStart(start, maximum, bounds) {
|
|
4523
|
+
compileWindowStart(start, maximum, bounds, context) {
|
|
2349
4524
|
const zero = () => this.module.i32.const(0);
|
|
2350
4525
|
if (bounds === "unchecked") {
|
|
2351
4526
|
return start;
|
|
@@ -2365,14 +4540,14 @@ class MirCompiler {
|
|
|
2365
4540
|
);
|
|
2366
4541
|
};
|
|
2367
4542
|
}
|
|
2368
|
-
if (bounds === "
|
|
4543
|
+
if (bounds === "checked") {
|
|
2369
4544
|
return () =>
|
|
2370
4545
|
this.module.if(
|
|
2371
4546
|
this.module.i32.or(
|
|
2372
4547
|
this.module.i32.lt_s(start(), zero()),
|
|
2373
4548
|
this.module.i32.gt_s(start(), maximum()),
|
|
2374
4549
|
),
|
|
2375
|
-
this.
|
|
4550
|
+
this.raiseRuntimeFailure(context),
|
|
2376
4551
|
start(),
|
|
2377
4552
|
);
|
|
2378
4553
|
}
|
|
@@ -2383,7 +4558,7 @@ class MirCompiler {
|
|
|
2383
4558
|
const scalar = this.sliceElementScalar(data.slice, context);
|
|
2384
4559
|
return this.loadScalar(
|
|
2385
4560
|
scalar,
|
|
2386
|
-
this.compileSliceAddress(data.slice, data.index, data.bounds, context),
|
|
4561
|
+
this.compileSliceAddress(data.slice, data.index, data.bounds, context, false),
|
|
2387
4562
|
);
|
|
2388
4563
|
}
|
|
2389
4564
|
|
|
@@ -2394,7 +4569,7 @@ class MirCompiler {
|
|
|
2394
4569
|
const scalar = this.sliceElementScalar(data.slice, context);
|
|
2395
4570
|
return this.storeScalar(
|
|
2396
4571
|
scalar,
|
|
2397
|
-
this.compileSliceAddress(data.slice, data.index, data.bounds, context),
|
|
4572
|
+
this.compileSliceAddress(data.slice, data.index, data.bounds, context, true),
|
|
2398
4573
|
this.compileValue(data.value, context),
|
|
2399
4574
|
);
|
|
2400
4575
|
}
|
|
@@ -2417,14 +4592,14 @@ class MirCompiler {
|
|
|
2417
4592
|
const scalarSize = this.scalarSize(scalar);
|
|
2418
4593
|
const address = () =>
|
|
2419
4594
|
this.module.i32.add(
|
|
2420
|
-
destination()[
|
|
2421
|
-
this.module.i32.mul(counterValue(), destination()[
|
|
4595
|
+
destination()[1],
|
|
4596
|
+
this.module.i32.mul(counterValue(), destination()[3]),
|
|
2422
4597
|
);
|
|
2423
4598
|
const scalarLoop = () =>
|
|
2424
4599
|
this.module.loop(
|
|
2425
4600
|
scalarLoopLabel,
|
|
2426
4601
|
this.module.if(
|
|
2427
|
-
this.module.i32.lt_s(counterValue(), destination()[
|
|
4602
|
+
this.module.i32.lt_s(counterValue(), destination()[2]),
|
|
2428
4603
|
this.module.block(null, [
|
|
2429
4604
|
this.storeScalar(
|
|
2430
4605
|
scalar,
|
|
@@ -2444,18 +4619,18 @@ class MirCompiler {
|
|
|
2444
4619
|
const lanes = 16 / scalarSize;
|
|
2445
4620
|
const vectorCondition = this.module.i32.and(
|
|
2446
4621
|
this.module.i32.eq(
|
|
2447
|
-
destination()[
|
|
4622
|
+
destination()[3],
|
|
2448
4623
|
this.module.i32.const(scalarSize),
|
|
2449
4624
|
),
|
|
2450
4625
|
this.module.i32.and(
|
|
2451
4626
|
this.module.i32.ge_u(
|
|
2452
|
-
destination()[
|
|
4627
|
+
destination()[2],
|
|
2453
4628
|
this.module.i32.const(lanes),
|
|
2454
4629
|
),
|
|
2455
4630
|
this.module.i32.le_u(
|
|
2456
4631
|
counterValue(),
|
|
2457
4632
|
this.module.i32.sub(
|
|
2458
|
-
destination()[
|
|
4633
|
+
destination()[2],
|
|
2459
4634
|
this.module.i32.const(lanes),
|
|
2460
4635
|
),
|
|
2461
4636
|
),
|
|
@@ -2515,8 +4690,8 @@ class MirCompiler {
|
|
|
2515
4690
|
const copyIndex = () =>
|
|
2516
4691
|
this.module.select(
|
|
2517
4692
|
this.module.i32.and(
|
|
2518
|
-
this.module.i32.eq(destination()[
|
|
2519
|
-
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]),
|
|
2520
4695
|
),
|
|
2521
4696
|
this.module.i32.sub(
|
|
2522
4697
|
this.module.i32.sub(countValue(), this.module.i32.const(1)),
|
|
@@ -2527,12 +4702,12 @@ class MirCompiler {
|
|
|
2527
4702
|
const sourceAddress = () =>
|
|
2528
4703
|
this.module.i32.add(
|
|
2529
4704
|
source()[0],
|
|
2530
|
-
this.module.i32.mul(copyIndex(), source()[
|
|
4705
|
+
this.module.i32.mul(copyIndex(), source()[3]),
|
|
2531
4706
|
);
|
|
2532
4707
|
const destinationAddress = () =>
|
|
2533
4708
|
this.module.i32.add(
|
|
2534
|
-
destination()[
|
|
2535
|
-
this.module.i32.mul(copyIndex(), destination()[
|
|
4709
|
+
destination()[1],
|
|
4710
|
+
this.module.i32.mul(copyIndex(), destination()[3]),
|
|
2536
4711
|
);
|
|
2537
4712
|
const sourceScalar = this.sliceElementScalar(data.source, context);
|
|
2538
4713
|
const destinationScalar = this.sliceElementScalar(data.destination, context);
|
|
@@ -2550,7 +4725,7 @@ class MirCompiler {
|
|
|
2550
4725
|
source()[0],
|
|
2551
4726
|
this.module.i32.mul(
|
|
2552
4727
|
this.module.i32.sub(countValue(), this.module.i32.const(1)),
|
|
2553
|
-
source()[
|
|
4728
|
+
source()[3],
|
|
2554
4729
|
),
|
|
2555
4730
|
),
|
|
2556
4731
|
this.module.i32.const(this.scalarSize(sourceScalar)),
|
|
@@ -2558,10 +4733,10 @@ class MirCompiler {
|
|
|
2558
4733
|
const destinationEnd = () =>
|
|
2559
4734
|
this.module.i32.add(
|
|
2560
4735
|
this.module.i32.add(
|
|
2561
|
-
destination()[
|
|
4736
|
+
destination()[1],
|
|
2562
4737
|
this.module.i32.mul(
|
|
2563
4738
|
this.module.i32.sub(countValue(), this.module.i32.const(1)),
|
|
2564
|
-
destination()[
|
|
4739
|
+
destination()[3],
|
|
2565
4740
|
),
|
|
2566
4741
|
),
|
|
2567
4742
|
this.module.i32.const(this.scalarSize(destinationScalar)),
|
|
@@ -2570,13 +4745,13 @@ class MirCompiler {
|
|
|
2570
4745
|
this.module.i32.and(
|
|
2571
4746
|
nonEmpty(),
|
|
2572
4747
|
this.module.i32.and(
|
|
2573
|
-
this.module.i32.lt_u(destination()[
|
|
4748
|
+
this.module.i32.lt_u(destination()[1], sourceEnd()),
|
|
2574
4749
|
this.module.i32.lt_u(source()[0], destinationEnd()),
|
|
2575
4750
|
),
|
|
2576
4751
|
);
|
|
2577
4752
|
const invalidOverlap = () =>
|
|
2578
4753
|
this.module.i32.and(
|
|
2579
|
-
this.module.i32.ne(destination()[
|
|
4754
|
+
this.module.i32.ne(destination()[3], source()[3]),
|
|
2580
4755
|
overlaps(),
|
|
2581
4756
|
);
|
|
2582
4757
|
const scalarCopy = () =>
|
|
@@ -2599,18 +4774,18 @@ class MirCompiler {
|
|
|
2599
4774
|
? this.module.if(
|
|
2600
4775
|
this.module.i32.and(
|
|
2601
4776
|
this.module.i32.eq(
|
|
2602
|
-
destination()[
|
|
4777
|
+
destination()[3],
|
|
2603
4778
|
this.module.i32.const(this.scalarSize(destinationScalar)),
|
|
2604
4779
|
),
|
|
2605
4780
|
this.module.i32.eq(
|
|
2606
|
-
source()[
|
|
4781
|
+
source()[3],
|
|
2607
4782
|
this.module.i32.const(this.scalarSize(sourceScalar)),
|
|
2608
4783
|
),
|
|
2609
4784
|
),
|
|
2610
4785
|
// memory.copy has memmove overlap semantics and lets engines use
|
|
2611
4786
|
// their tuned bulk-memory implementation for contiguous slices.
|
|
2612
4787
|
this.module.memory.copy(
|
|
2613
|
-
destination()[
|
|
4788
|
+
destination()[1],
|
|
2614
4789
|
source()[0],
|
|
2615
4790
|
this.module.i32.mul(
|
|
2616
4791
|
countValue(),
|
|
@@ -2624,12 +4799,12 @@ class MirCompiler {
|
|
|
2624
4799
|
this.module.local.set(
|
|
2625
4800
|
count,
|
|
2626
4801
|
this.module.select(
|
|
2627
|
-
this.module.i32.lt_s(destination()[
|
|
2628
|
-
destination()[
|
|
2629
|
-
source()[
|
|
4802
|
+
this.module.i32.lt_s(destination()[2], source()[2]),
|
|
4803
|
+
destination()[2],
|
|
4804
|
+
source()[2],
|
|
2630
4805
|
),
|
|
2631
4806
|
),
|
|
2632
|
-
this.module.if(invalidOverlap(), this.
|
|
4807
|
+
this.module.if(invalidOverlap(), this.raiseRuntimeFailure(context)),
|
|
2633
4808
|
this.module.local.set(counter, this.module.i32.const(0)),
|
|
2634
4809
|
copy,
|
|
2635
4810
|
]);
|
|
@@ -2892,7 +5067,7 @@ class MirCompiler {
|
|
|
2892
5067
|
),
|
|
2893
5068
|
);
|
|
2894
5069
|
}
|
|
2895
|
-
if (bounds === "
|
|
5070
|
+
if (bounds === "checked") {
|
|
2896
5071
|
const outOfBounds = this.module.i32.or(
|
|
2897
5072
|
this.module.i32.lt_s(
|
|
2898
5073
|
this.compileValue(value, context),
|
|
@@ -2905,14 +5080,20 @@ class MirCompiler {
|
|
|
2905
5080
|
);
|
|
2906
5081
|
return this.module.if(
|
|
2907
5082
|
outOfBounds,
|
|
2908
|
-
this.
|
|
5083
|
+
this.raiseRuntimeFailure(context),
|
|
2909
5084
|
this.compileValue(value, context),
|
|
2910
5085
|
);
|
|
2911
5086
|
}
|
|
2912
5087
|
this.fail(`unknown bounds mode '${String(bounds)}'`);
|
|
2913
5088
|
}
|
|
2914
5089
|
|
|
2915
|
-
compileDynamicBoundedIndex(
|
|
5090
|
+
compileDynamicBoundedIndex(
|
|
5091
|
+
index,
|
|
5092
|
+
length,
|
|
5093
|
+
bounds,
|
|
5094
|
+
context,
|
|
5095
|
+
clampLengthKnownPositive = false,
|
|
5096
|
+
) {
|
|
2916
5097
|
if (bounds === "unchecked") {
|
|
2917
5098
|
return index();
|
|
2918
5099
|
}
|
|
@@ -2932,17 +5113,17 @@ class MirCompiler {
|
|
|
2932
5113
|
if (clampLengthKnownPositive) return clamped();
|
|
2933
5114
|
return this.module.if(
|
|
2934
5115
|
this.module.i32.le_s(length(), this.module.i32.const(0)),
|
|
2935
|
-
this.
|
|
5116
|
+
this.raiseRuntimeFailure(context),
|
|
2936
5117
|
clamped(),
|
|
2937
5118
|
);
|
|
2938
5119
|
}
|
|
2939
|
-
if (bounds === "
|
|
5120
|
+
if (bounds === "checked") {
|
|
2940
5121
|
return this.module.if(
|
|
2941
5122
|
this.module.i32.or(
|
|
2942
5123
|
this.module.i32.lt_s(index(), this.module.i32.const(0)),
|
|
2943
5124
|
this.module.i32.ge_s(index(), length()),
|
|
2944
5125
|
),
|
|
2945
|
-
this.
|
|
5126
|
+
this.raiseRuntimeFailure(context),
|
|
2946
5127
|
index(),
|
|
2947
5128
|
);
|
|
2948
5129
|
}
|
|
@@ -2966,7 +5147,7 @@ class MirCompiler {
|
|
|
2966
5147
|
}
|
|
2967
5148
|
}
|
|
2968
5149
|
|
|
2969
|
-
compileBinary(op, scalar, lhs, rhs) {
|
|
5150
|
+
compileBinary(op, scalar, lhs, rhs, context) {
|
|
2970
5151
|
if (!supportsMirOperation("binary", op, scalar)) {
|
|
2971
5152
|
this.fail(`binary operation '${String(op)}' does not support scalar '${scalar}'`);
|
|
2972
5153
|
}
|
|
@@ -2990,13 +5171,26 @@ class MirCompiler {
|
|
|
2990
5171
|
wasm.eq(lhs(), minimum()),
|
|
2991
5172
|
wasm.eq(rhs(), negativeOne()),
|
|
2992
5173
|
);
|
|
2993
|
-
|
|
5174
|
+
const division = this.module.if(
|
|
5175
|
+
overflow,
|
|
5176
|
+
minimum(),
|
|
5177
|
+
wasm.div_s(lhs(), rhs()),
|
|
5178
|
+
);
|
|
5179
|
+
return this.module.if(
|
|
5180
|
+
wasm.eq(rhs(), this.zero(scalar)),
|
|
5181
|
+
this.raiseRuntimeFailure(context),
|
|
5182
|
+
division,
|
|
5183
|
+
);
|
|
2994
5184
|
}
|
|
2995
5185
|
case "remainder":
|
|
2996
5186
|
if (!integer) {
|
|
2997
5187
|
return this.compileMathKernelCall("remainder", scalar, [lhs(), rhs()]);
|
|
2998
5188
|
}
|
|
2999
|
-
return
|
|
5189
|
+
return this.module.if(
|
|
5190
|
+
wasm.eq(rhs(), this.zero(scalar)),
|
|
5191
|
+
this.raiseRuntimeFailure(context),
|
|
5192
|
+
wasm.rem_s(lhs(), rhs()),
|
|
5193
|
+
);
|
|
3000
5194
|
case "bit_and": return wasm.and(lhs(), rhs());
|
|
3001
5195
|
case "bit_or": return wasm.or(lhs(), rhs());
|
|
3002
5196
|
case "bit_xor": return wasm.xor(lhs(), rhs());
|
|
@@ -3083,6 +5277,16 @@ class MirCompiler {
|
|
|
3083
5277
|
return this.module.select(wasm.lt_s(arg(0), arg(1)), arg(0), arg(1));
|
|
3084
5278
|
case "max":
|
|
3085
5279
|
return this.module.select(wasm.gt_s(arg(0), arg(1)), arg(0), arg(1));
|
|
5280
|
+
case "range_clamp":
|
|
5281
|
+
return this.module.select(
|
|
5282
|
+
wasm.lt_s(arg(0), arg(1)),
|
|
5283
|
+
arg(1),
|
|
5284
|
+
this.module.select(
|
|
5285
|
+
wasm.gt_s(arg(0), arg(2)),
|
|
5286
|
+
arg(2),
|
|
5287
|
+
arg(0),
|
|
5288
|
+
),
|
|
5289
|
+
);
|
|
3086
5290
|
default:
|
|
3087
5291
|
this.fail(`intrinsic '${data.intrinsic}' requires f32 or f64 operands`);
|
|
3088
5292
|
}
|
|
@@ -3096,6 +5300,22 @@ class MirCompiler {
|
|
|
3096
5300
|
case "trunc": return wasm.trunc(args[0]);
|
|
3097
5301
|
case "min": return wasm.min(args[0], args[1]);
|
|
3098
5302
|
case "max": return wasm.max(args[0], args[1]);
|
|
5303
|
+
case "range_clamp": {
|
|
5304
|
+
const arg = (index) => this.compileValue(data.args[index], context);
|
|
5305
|
+
return this.module.select(
|
|
5306
|
+
wasm.ne(arg(0), arg(0)),
|
|
5307
|
+
arg(1),
|
|
5308
|
+
this.module.select(
|
|
5309
|
+
wasm.lt(arg(0), arg(1)),
|
|
5310
|
+
arg(1),
|
|
5311
|
+
this.module.select(
|
|
5312
|
+
wasm.gt(arg(0), arg(2)),
|
|
5313
|
+
arg(2),
|
|
5314
|
+
arg(0),
|
|
5315
|
+
),
|
|
5316
|
+
),
|
|
5317
|
+
);
|
|
5318
|
+
}
|
|
3099
5319
|
case "fma": return this.compileMathKernelCall(data.intrinsic, scalar, args);
|
|
3100
5320
|
case "sin":
|
|
3101
5321
|
case "cos":
|
|
@@ -3205,6 +5425,12 @@ class MirCompiler {
|
|
|
3205
5425
|
lhs.data.element === rhs.data.element &&
|
|
3206
5426
|
JSON.stringify(lhs.data.channels) === JSON.stringify(rhs.data.channels) &&
|
|
3207
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;
|
|
3208
5434
|
} else if (lhs.kind === "tuple") {
|
|
3209
5435
|
equivalent =
|
|
3210
5436
|
lhs.data.length === rhs.data.length &&
|
|
@@ -3311,6 +5537,85 @@ class MirCompiler {
|
|
|
3311
5537
|
return this.mir.interface.buffers[id];
|
|
3312
5538
|
}
|
|
3313
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
|
+
|
|
3314
5619
|
currentLabel(labels, statement) {
|
|
3315
5620
|
const label = labels.at(-1);
|
|
3316
5621
|
if (!label) this.fail(`'${statement}' appears outside a MIR loop`);
|
|
@@ -3418,6 +5723,7 @@ class MirCompiler {
|
|
|
3418
5723
|
default_reprs: null,
|
|
3419
5724
|
range_min_repr: null,
|
|
3420
5725
|
range_max_repr: null,
|
|
5726
|
+
param_control: null,
|
|
3421
5727
|
})),
|
|
3422
5728
|
params: this.mir.interface.params.map((param, id) => ({
|
|
3423
5729
|
name: param.name,
|
|
@@ -3432,9 +5738,18 @@ class MirCompiler {
|
|
|
3432
5738
|
default_reprs: this.constantReprs(param.default),
|
|
3433
5739
|
range_min_repr: this.scalarRepr(param.range?.min),
|
|
3434
5740
|
range_max_repr: this.scalarRepr(param.range?.max),
|
|
5741
|
+
param_control: this.storageShape(param.ty).length === 1 && param.range
|
|
5742
|
+
? {
|
|
5743
|
+
scale: param.control.scale,
|
|
5744
|
+
curve: param.control.curve,
|
|
5745
|
+
unit: param.control.unit,
|
|
5746
|
+
step_repr: this.scalarRepr(param.control.step),
|
|
5747
|
+
step_count: param.control.step_count,
|
|
5748
|
+
}
|
|
5749
|
+
: null,
|
|
3435
5750
|
})),
|
|
3436
|
-
buffers: this.mir.interface.buffers.map((buffer) => {
|
|
3437
|
-
const channels = this.bufferChannelMetadata(buffer.channels);
|
|
5751
|
+
buffers: this.mir.interface.buffers.map((buffer, bufferId) => {
|
|
5752
|
+
const channels = this.bufferChannelMetadata(buffer.channels, buffer.element);
|
|
3438
5753
|
return {
|
|
3439
5754
|
name: buffer.name,
|
|
3440
5755
|
type_repr: this.bufferTypeRepr(buffer, channels),
|
|
@@ -3443,9 +5758,14 @@ class MirCompiler {
|
|
|
3443
5758
|
channels: channels.kind,
|
|
3444
5759
|
static_channels: channels.count,
|
|
3445
5760
|
access: buffer.access,
|
|
3446
|
-
may_write:
|
|
5761
|
+
may_write: this.bufferMayWrite[bufferId],
|
|
3447
5762
|
};
|
|
3448
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
|
+
})),
|
|
3449
5769
|
events: this.mir.interface.events.map((event, eventId) => ({
|
|
3450
5770
|
name: event.name,
|
|
3451
5771
|
export: `onda_event_${eventId}`,
|
|
@@ -3509,6 +5829,7 @@ class MirCompiler {
|
|
|
3509
5829
|
default_reprs: null,
|
|
3510
5830
|
range_min_repr: null,
|
|
3511
5831
|
range_max_repr: null,
|
|
5832
|
+
param_control: null,
|
|
3512
5833
|
}));
|
|
3513
5834
|
}
|
|
3514
5835
|
|
|
@@ -3536,11 +5857,11 @@ class MirCompiler {
|
|
|
3536
5857
|
}
|
|
3537
5858
|
|
|
3538
5859
|
bufferTypeRepr(buffer, channels) {
|
|
3539
|
-
if (channels.kind === "mono") return `buffer
|
|
5860
|
+
if (channels.kind === "mono") return `buffer<${buffer.element}>`;
|
|
3540
5861
|
if (channels.kind === "static") {
|
|
3541
|
-
return `buffer
|
|
5862
|
+
return `buffer<${buffer.element}[${channels.count}]>`;
|
|
3542
5863
|
}
|
|
3543
|
-
return `buffer
|
|
5864
|
+
return `buffer<${buffer.element}[]>`;
|
|
3544
5865
|
}
|
|
3545
5866
|
|
|
3546
5867
|
storageShape(typeId) {
|
|
@@ -3566,7 +5887,7 @@ class MirCompiler {
|
|
|
3566
5887
|
this.fail(`storage metadata for MIR type '${type.kind}' is not supported yet`);
|
|
3567
5888
|
}
|
|
3568
5889
|
|
|
3569
|
-
bufferChannelMetadata(channels) {
|
|
5890
|
+
bufferChannelMetadata(channels, element) {
|
|
3570
5891
|
if (channels === "mono") {
|
|
3571
5892
|
return { kind: "mono", count: 1 };
|
|
3572
5893
|
}
|
|
@@ -3577,7 +5898,8 @@ class MirCompiler {
|
|
|
3577
5898
|
channels &&
|
|
3578
5899
|
typeof channels === "object" &&
|
|
3579
5900
|
Number.isInteger(channels.static) &&
|
|
3580
|
-
channels.static > 0
|
|
5901
|
+
channels.static > 0 &&
|
|
5902
|
+
channels.static <= Math.floor(0x7fffffff / this.scalarSize(element))
|
|
3581
5903
|
) {
|
|
3582
5904
|
return { kind: "static", count: channels.static };
|
|
3583
5905
|
}
|