@bytecodealliance/jco 1.16.1 → 1.17.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.
@@ -1,4 +1,5 @@
1
- "use jco";import { environment, exit as exit$1, stderr, stdin, stdout, terminalInput, terminalOutput, terminalStderr, terminalStdin, terminalStdout } from '@bytecodealliance/preview2-shim/cli';
1
+ "use jco";
2
+ import { environment, exit as exit$1, stderr, stdin, stdout, terminalInput, terminalOutput, terminalStderr, terminalStdin, terminalStdout } from '@bytecodealliance/preview2-shim/cli';
2
3
  import { preopens, types } from '@bytecodealliance/preview2-shim/filesystem';
3
4
  import { error, streams } from '@bytecodealliance/preview2-shim/io';
4
5
  import { random } from '@bytecodealliance/preview2-shim/random';
@@ -165,1791 +166,2102 @@ if (getRandomBytes=== undefined) {
165
166
  }
166
167
 
167
168
 
168
- let dv = new DataView(new ArrayBuffer());
169
- const dataView = mem => dv.buffer === mem.buffer ? dv : dv = new DataView(mem.buffer);
170
-
171
- const toUint64 = val => BigInt.asUintN(64, BigInt(val));
172
-
173
- function toUint32(val) {
174
- return val >>> 0;
169
+ const _debugLog = (...args) => {
170
+ if (!globalThis?.process?.env?.JCO_DEBUG) { return; }
171
+ console.debug(...args);
175
172
  }
176
- const TEXT_DECODER_UTF8 = new TextDecoder();
177
- const TEXT_ENCODER_UTF8 = new TextEncoder();
173
+ const ASYNC_DETERMINISM = 'random';
178
174
 
179
- function _utf8AllocateAndEncode(s, realloc, memory) {
180
- if (typeof s !== 'string') {
181
- throw new TypeError('expected a string, received [' + typeof s + ']');
175
+ class GlobalComponentAsyncLowers {
176
+ static map = new Map();
177
+
178
+ constructor() { throw new Error('GlobalComponentAsyncLowers should not be constructed'); }
179
+
180
+ static define(args) {
181
+ const { componentIdx, qualifiedImportFn, fn } = args;
182
+ let inner = GlobalComponentAsyncLowers.map.get(componentIdx);
183
+ if (!inner) {
184
+ inner = new Map();
185
+ GlobalComponentAsyncLowers.map.set(componentIdx, inner);
186
+ }
187
+
188
+ inner.set(qualifiedImportFn, fn);
182
189
  }
183
- if (s.length === 0) { return { ptr: 1, len: 0 }; }
184
- let buf = TEXT_ENCODER_UTF8.encode(s);
185
- let ptr = realloc(0, 0, 1, buf.length);
186
- new Uint8Array(memory.buffer).set(buf, ptr);
187
- return { ptr, len: buf.length, codepoints: [...s].length };
188
- }
189
-
190
-
191
- const T_FLAG = 1 << 30;
192
-
193
- function rscTableCreateOwn (table, rep) {
194
- const free = table[0] & ~T_FLAG;
195
- if (free === 0) {
196
- table.push(0);
197
- table.push(rep | T_FLAG);
198
- return (table.length >> 1) - 1;
190
+
191
+ static lookup(componentIdx, qualifiedImportFn) {
192
+ let inner = GlobalComponentAsyncLowers.map.get(componentIdx);
193
+ if (!inner) {
194
+ inner = new Map();
195
+ GlobalComponentAsyncLowers.map.set(componentIdx, inner);
196
+ }
197
+
198
+ const found = inner.get(qualifiedImportFn);
199
+ if (found) { return found; }
200
+
201
+ // In some cases, async lowers are *not* host provided, and
202
+ // but contain/will call an async function in the host.
203
+ //
204
+ // One such case is `stream.write`/`stream.read` trampolines which are
205
+ // actually re-exported through a patch up container *before*
206
+ // they call the relevant async host trampoline.
207
+ //
208
+ // So the path of execution from a component export would be:
209
+ //
210
+ // async guest export --> stream.write import (host wired) -> guest export (patch component) -> async host trampoline
211
+ //
212
+ // On top of all this, the trampoline that is eventually called is async,
213
+ // so we must await the patched guest export call.
214
+ //
215
+ if (qualifiedImportFn.includes("[stream-write-") || qualifiedImportFn.includes("[stream-read-")) {
216
+ return async (...args) => {
217
+ const [originalFn, ...params] = args;
218
+ return await originalFn(...params);
219
+ };
220
+ }
221
+
222
+ // All other cases can call the registered function directly
223
+ return (...args) => {
224
+ const [originalFn, ...params] = args;
225
+ return originalFn(...params);
226
+ };
199
227
  }
200
- table[0] = table[free << 1];
201
- table[free << 1] = 0;
202
- table[(free << 1) + 1] = rep | T_FLAG;
203
- return free;
204
- }
205
-
206
- function rscTableRemove (table, handle) {
207
- const scope = table[handle << 1];
208
- const val = table[(handle << 1) + 1];
209
- const own = (val & T_FLAG) !== 0;
210
- const rep = val & ~T_FLAG;
211
- if (val === 0 || (scope & T_FLAG) !== 0) throw new TypeError('Invalid handle');
212
- table[handle << 1] = table[0] | T_FLAG;
213
- table[0] = handle | T_FLAG;
214
- return { rep, scope, own };
215
228
  }
216
229
 
217
- let curResourceBorrows = [];
218
-
219
- function getCurrentTask(componentIdx) {
220
- if (componentIdx === undefined || componentIdx === null) {
221
- throw new Error('missing/invalid component instance index [' + componentIdx + '] while getting current task');
230
+ class GlobalAsyncParamLowers {
231
+ static map = new Map();
232
+
233
+ static generateKey(args) {
234
+ const { componentIdx, iface, fnName } = args;
235
+ if (componentIdx === undefined) { throw new TypeError("missing component idx"); }
236
+ if (iface === undefined) { throw new TypeError("missing iface name"); }
237
+ if (fnName === undefined) { throw new TypeError("missing function name"); }
238
+ return `${componentIdx}-${iface}-${fnName}`;
222
239
  }
223
- const tasks = ASYNC_TASKS_BY_COMPONENT_IDX.get(componentIdx);
224
- if (tasks === undefined) { return undefined; }
225
- if (tasks.length === 0) { return undefined; }
226
- return tasks[tasks.length - 1];
227
- }
228
-
229
- function createNewCurrentTask(args) {
230
- _debugLog('[createNewCurrentTask()] args', args);
231
- const {
232
- componentIdx,
233
- isAsync,
234
- entryFnName,
235
- parentSubtaskID,
236
- callbackFnName,
237
- getCallbackFn,
238
- getParamsFn,
239
- stringEncoding,
240
- errHandling,
241
- getCalleeParamsFn,
242
- resultPtr,
243
- callingWasmExport,
244
- } = args;
245
- if (componentIdx === undefined || componentIdx === null) {
246
- throw new Error('missing/invalid component instance index while starting task');
240
+
241
+ static define(args) {
242
+ const { componentIdx, iface, fnName, fn } = args;
243
+ if (!fn) { throw new TypeError('missing function'); }
244
+ const key = GlobalAsyncParamLowers.generateKey(args);
245
+ GlobalAsyncParamLowers.map.set(key, fn);
247
246
  }
248
- const taskMetas = ASYNC_TASKS_BY_COMPONENT_IDX.get(componentIdx);
249
- const callbackFn = getCallbackFn ? getCallbackFn() : null;
250
247
 
251
- const newTask = new AsyncTask({
252
- componentIdx,
253
- isAsync,
254
- entryFnName,
255
- callbackFn,
256
- callbackFnName,
257
- stringEncoding,
258
- getCalleeParamsFn,
259
- resultPtr,
260
- errHandling,
261
- });
248
+ static lookup(args) {
249
+ const { componentIdx, iface, fnName } = args;
250
+ const key = GlobalAsyncParamLowers.generateKey(args);
251
+ return GlobalAsyncParamLowers.map.get(key);
252
+ }
253
+ }
254
+
255
+ class GlobalComponentMemories {
256
+ static map = new Map();
262
257
 
263
- const newTaskID = newTask.id();
264
- const newTaskMeta = { id: newTaskID, componentIdx, task: newTask };
258
+ constructor() { throw new Error('GlobalComponentMemories should not be constructed'); }
265
259
 
266
- ASYNC_CURRENT_TASK_IDS.push(newTaskID);
267
- ASYNC_CURRENT_COMPONENT_IDXS.push(componentIdx);
260
+ static save(args) {
261
+ const { idx, componentIdx, memory } = args;
262
+ let inner = GlobalComponentMemories.map.get(componentIdx);
263
+ if (!inner) {
264
+ inner = [];
265
+ GlobalComponentMemories.map.set(componentIdx, inner);
266
+ }
267
+ inner.push({ memory, idx });
268
+ }
268
269
 
269
- if (!taskMetas) {
270
- ASYNC_TASKS_BY_COMPONENT_IDX.set(componentIdx, [newTaskMeta]);
271
- } else {
272
- taskMetas.push(newTaskMeta);
270
+ static getMemoriesForComponentIdx(componentIdx) {
271
+ const metas = GlobalComponentMemories.map.get(componentIdx);
272
+ return metas.map(meta => meta.memory);
273
273
  }
274
274
 
275
- return [newTask, newTaskID];
275
+ static getMemory(componentIdx, idx) {
276
+ const metas = GlobalComponentMemories.map.get(componentIdx);
277
+ return metas.find(meta => meta.idx === idx)?.memory;
278
+ }
276
279
  }
277
280
 
278
- function endCurrentTask(componentIdx, taskID) {
279
- componentIdx ??= ASYNC_CURRENT_COMPONENT_IDXS.at(-1);
280
- taskID ??= ASYNC_CURRENT_TASK_IDS.at(-1);
281
- _debugLog('[endCurrentTask()] args', { componentIdx, taskID });
281
+ class RepTable {
282
+ #data = [0, null];
283
+ #target;
282
284
 
283
- if (componentIdx === undefined || componentIdx === null) {
284
- throw new Error('missing/invalid component instance index while ending current task');
285
+ constructor(args) {
286
+ this.target = args?.target;
285
287
  }
286
288
 
287
- const tasks = ASYNC_TASKS_BY_COMPONENT_IDX.get(componentIdx);
288
- if (!tasks || !Array.isArray(tasks)) {
289
- throw new Error('missing/invalid tasks for component instance while ending task');
289
+ insert(val) {
290
+ _debugLog('[RepTable#insert()] args', { val, target: this.target });
291
+ const freeIdx = this.#data[0];
292
+ if (freeIdx === 0) {
293
+ this.#data.push(val);
294
+ this.#data.push(null);
295
+ return (this.#data.length >> 1) - 1;
296
+ }
297
+ this.#data[0] = this.#data[freeIdx << 1];
298
+ const placementIdx = freeIdx << 1;
299
+ this.#data[placementIdx] = val;
300
+ this.#data[placementIdx + 1] = null;
301
+ return freeIdx;
290
302
  }
291
- if (tasks.length == 0) {
292
- throw new Error('no current task(s) for component instance while ending task');
303
+
304
+ get(rep) {
305
+ _debugLog('[RepTable#get()] args', { rep, target: this.target });
306
+ const baseIdx = rep << 1;
307
+ const val = this.#data[baseIdx];
308
+ return val;
293
309
  }
294
310
 
295
- if (taskID) {
296
- const last = tasks[tasks.length - 1];
297
- if (last.id !== taskID) {
298
- // throw new Error('current task does not match expected task ID');
299
- return;
300
- }
311
+ contains(rep) {
312
+ _debugLog('[RepTable#contains()] args', { rep, target: this.target });
313
+ const baseIdx = rep << 1;
314
+ return !!this.#data[baseIdx];
301
315
  }
302
316
 
303
- ASYNC_CURRENT_TASK_IDS.pop();
304
- ASYNC_CURRENT_COMPONENT_IDXS.pop();
317
+ remove(rep) {
318
+ _debugLog('[RepTable#remove()] args', { rep, target: this.target });
319
+ if (this.#data.length === 2) { throw new Error('invalid'); }
320
+
321
+ const baseIdx = rep << 1;
322
+ const val = this.#data[baseIdx];
323
+ if (val === 0) { throw new Error('invalid resource rep (cannot be 0)'); }
324
+
325
+ this.#data[baseIdx] = this.#data[0];
326
+ this.#data[0] = rep;
327
+
328
+ return val;
329
+ }
305
330
 
306
- const taskMeta = tasks.pop();
307
- return taskMeta.task;
331
+ clear() {
332
+ _debugLog('[RepTable#clear()] args', { rep, target: this.target });
333
+ this.#data = [0, null];
334
+ }
308
335
  }
309
- const ASYNC_TASKS_BY_COMPONENT_IDX = new Map();
336
+ const _coinFlip = () => { return Math.random() > 0.5; };
337
+ let SCOPE_ID = 0;
338
+ const I32_MIN = -2_147_483_648;
339
+ const I32_MAX = 2_147_483_647;
340
+ const _typeCheckValidI32 = (n) => typeof n === 'number' && n >= I32_MIN && n <= I32_MAX;
341
+
342
+ const _typeCheckAsyncFn= (f) => {
343
+ return f instanceof ASYNC_FN_CTOR;
344
+ };
345
+
346
+ const ASYNC_FN_CTOR = (async () => {}).constructor;
310
347
  const ASYNC_CURRENT_TASK_IDS = [];
311
348
  const ASYNC_CURRENT_COMPONENT_IDXS = [];
312
349
 
313
- class AsyncTask {
314
- static _ID = 0n;
315
-
316
- static State = {
317
- INITIAL: 'initial',
318
- CANCELLED: 'cancelled',
319
- CANCEL_PENDING: 'cancel-pending',
320
- CANCEL_DELIVERED: 'cancel-delivered',
321
- RESOLVED: 'resolved',
322
- }
323
-
324
- static BlockResult = {
325
- CANCELLED: 'block.cancelled',
326
- NOT_CANCELLED: 'block.not-cancelled',
350
+ function unpackCallbackResult(result) {
351
+ _debugLog('[unpackCallbackResult()] args', { result });
352
+ if (!(_typeCheckValidI32(result))) { throw new Error('invalid callback return value [' + result + '], not a valid i32'); }
353
+ const eventCode = result & 0xF;
354
+ if (eventCode < 0 || eventCode > 3) {
355
+ throw new Error('invalid async return value [' + eventCode + '], outside callback code range');
327
356
  }
357
+ if (result < 0 || result >= 2**32) { throw new Error('invalid callback result'); }
358
+ // TODO: table max length check?
359
+ const waitableSetRep = result >> 4;
360
+ return [eventCode, waitableSetRep];
361
+ }
362
+
363
+ function promiseWithResolvers() {
364
+ if (Promise.withResolvers) {
365
+ return Promise.withResolvers();
366
+ } else {
367
+ let resolve;
368
+ let reject;
369
+ const promise = new Promise((res, rej) => {
370
+ resolve = res;
371
+ reject = rej;
372
+ });
373
+ return { promise, resolve, reject };
374
+ }
375
+ }
376
+
377
+ function _prepareCall(
378
+ memoryIdx,
379
+ getMemoryFn,
380
+ startFn,
381
+ returnFn,
382
+ callerInstanceIdx,
383
+ calleeInstanceIdx,
384
+ taskReturnTypeIdx,
385
+ isCalleeAsyncInt,
386
+ stringEncoding,
387
+ resultCountOrAsync,
388
+ ) {
389
+ _debugLog('[_prepareCall()]', {
390
+ callerInstanceIdx,
391
+ calleeInstanceIdx,
392
+ taskReturnTypeIdx,
393
+ isCalleeAsyncInt,
394
+ stringEncoding,
395
+ resultCountOrAsync,
396
+ });
397
+ const argArray = [...arguments];
328
398
 
329
- #id;
330
- #componentIdx;
331
- #state;
332
- #isAsync;
333
- #entryFnName = null;
334
- #subtasks = [];
335
-
336
- #onResolveHandlers = [];
337
- #completionPromise = null;
338
-
339
- #memoryIdx = null;
340
-
341
- #callbackFn = null;
342
- #callbackFnName = null;
343
-
344
- #postReturnFn = null;
345
-
346
- #getCalleeParamsFn = null;
347
-
348
- #stringEncoding = null;
399
+ // Since Rust will happily pass large u32s over, resultCountOrAsync should be one of:
400
+ // (a) u32 max size => callee is async fn with no result
401
+ // (b) u32 max size - 1 => callee is async fn with result
402
+ // (c) any other value => callee is sync with the given result count
403
+ //
404
+ // Due to JS handling the value as 2s complement, the `resultCountOrAsync` ends up being:
405
+ // (a) -1 as u32 max size
406
+ // (b) -2 as u32 max size - 1
407
+ // (c) x
408
+ //
409
+ // Due to JS mishandling the value as 2s complement, the actual values we get are:
410
+ // see. https://github.com/wasm-bindgen/wasm-bindgen/issues/1388
411
+ let isAsync = false;
412
+ let hasResultPointer = false;
413
+ if (resultCountOrAsync === -1) {
414
+ isAsync = true;
415
+ hasResultPointer = false;
416
+ } else if (resultCountOrAsync === -2) {
417
+ isAsync = true;
418
+ hasResultPointer = true;
419
+ }
349
420
 
350
- #parentSubtask = null;
421
+ const currentCallerTaskMeta = getCurrentTask(callerInstanceIdx);
422
+ if (!currentCallerTaskMeta) {
423
+ throw new Error('invalid/missing current task for caller during prepare call');
424
+ }
351
425
 
352
- #needsExclusiveLock = false;
426
+ const currentCallerTask = currentCallerTaskMeta.task;
427
+ if (!currentCallerTask) {
428
+ throw new Error('unexpectedly missing task in meta for caller during prepare call');
429
+ }
353
430
 
354
- #errHandling;
431
+ if (currentCallerTask.componentIdx() !== callerInstanceIdx) {
432
+ throw new Error(`task component idx [${ currentCallerTask.componentIdx() }] !== [${ callerInstanceIdx }] (callee ${ calleeInstanceIdx })`);
433
+ }
355
434
 
356
- #backpressurePromise;
357
- #backpressureWaiters = 0n;
435
+ let getCalleeParamsFn;
436
+ let resultPtr = null;
437
+ if (hasResultPointer) {
438
+ const directParamsArr = argArray.slice(11);
439
+ getCalleeParamsFn = () => directParamsArr;
440
+ resultPtr = argArray[10];
441
+ } else {
442
+ const directParamsArr = argArray.slice(10);
443
+ getCalleeParamsFn = () => directParamsArr;
444
+ }
358
445
 
359
- #returnLowerFns = null;
446
+ let encoding;
447
+ switch (stringEncoding) {
448
+ case 0:
449
+ encoding = 'utf8';
450
+ break;
451
+ case 1:
452
+ encoding = 'utf16';
453
+ break;
454
+ case 2:
455
+ encoding = 'compact-utf16';
456
+ break;
457
+ default:
458
+ throw new Error(`unrecognized string encoding enum [${stringEncoding}]`);
459
+ }
360
460
 
361
- cancelled = false;
362
- requested = false;
363
- alwaysTaskReturn = false;
461
+ const [newTask, newTaskID] = createNewCurrentTask({
462
+ componentIdx: calleeInstanceIdx,
463
+ isAsync: isCalleeAsyncInt !== 0,
464
+ getCalleeParamsFn,
465
+ // TODO: find a way to pass the import name through here
466
+ entryFnName: 'task/' + currentCallerTask.id() + '/new-prepare-task',
467
+ stringEncoding,
468
+ });
364
469
 
365
- returnCalls = 0;
366
- storage = [0, 0];
367
- borrowedHandles = {};
470
+ const subtask = currentCallerTask.createSubtask({
471
+ componentIdx: callerInstanceIdx,
472
+ parentTask: currentCallerTask,
473
+ childTask: newTask,
474
+ callMetadata: {
475
+ memory: getMemoryFn(),
476
+ memoryIdx,
477
+ resultPtr,
478
+ returnFn,
479
+ startFn,
480
+ }
481
+ });
368
482
 
369
- awaitableResume = null;
370
- awaitableCancel = null;
483
+ newTask.setParentSubtask(subtask);
484
+ // NOTE: This isn't really a return memory idx for the caller, it's for checking
485
+ // against the task.return (which will be called from the callee)
486
+ newTask.setReturnMemoryIdx(memoryIdx);
487
+ }
488
+
489
+ function _asyncStartCall(args, callee, paramCount, resultCount, flags) {
490
+ const { getCallbackFn, callbackIdx, getPostReturnFn, postReturnIdx } = args;
491
+ _debugLog('[_asyncStartCall()] args', args);
371
492
 
372
- constructor(opts) {
373
- this.#id = ++AsyncTask._ID;
374
-
375
- if (opts?.componentIdx === undefined) {
376
- throw new TypeError('missing component id during task creation');
377
- }
378
- this.#componentIdx = opts.componentIdx;
379
-
380
- this.#state = AsyncTask.State.INITIAL;
381
- this.#isAsync = opts?.isAsync ?? false;
382
- this.#entryFnName = opts.entryFnName;
383
-
384
- const {
385
- promise: completionPromise,
386
- resolve: resolveCompletionPromise,
387
- reject: rejectCompletionPromise,
388
- } = promiseWithResolvers();
389
- this.#completionPromise = completionPromise;
390
-
391
- this.#onResolveHandlers.push((results) => {
392
- resolveCompletionPromise(results);
393
- })
394
-
395
- if (opts.callbackFn) { this.#callbackFn = opts.callbackFn; }
396
- if (opts.callbackFnName) { this.#callbackFnName = opts.callbackFnName; }
397
-
398
- if (opts.getCalleeParamsFn) { this.#getCalleeParamsFn = opts.getCalleeParamsFn; }
399
-
400
- if (opts.stringEncoding) { this.#stringEncoding = opts.stringEncoding; }
401
-
402
- if (opts.parentSubtask) { this.#parentSubtask = opts.parentSubtask; }
403
-
404
- this.#needsExclusiveLock = this.isSync() || !this.hasCallback();
405
-
406
- if (opts.errHandling) { this.#errHandling = opts.errHandling; }
407
- }
493
+ const taskMeta = getCurrentTask(ASYNC_CURRENT_COMPONENT_IDXS.at(-1), ASYNC_CURRENT_TASK_IDS.at(-1));
494
+ if (!taskMeta) { throw new Error('invalid/missing current async task meta during prepare call'); }
408
495
 
409
- taskState() { return this.#state; }
410
- id() { return this.#id; }
411
- componentIdx() { return this.#componentIdx; }
412
- isAsync() { return this.#isAsync; }
413
- entryFnName() { return this.#entryFnName; }
414
- completionPromise() { return this.#completionPromise; }
496
+ const argArray = [...arguments];
415
497
 
416
- isAsync() { return this.#isAsync; }
417
- isSync() { return !this.isAsync(); }
498
+ // NOTE: at this point we know the current task is the one that was started
499
+ // in PrepareCall, so we *should* be able to pop it back off and be left with
500
+ // the previous task
501
+ const preparedTask = taskMeta.task;
502
+ if (!preparedTask) { throw new Error('unexpectedly missing task in task meta during prepare call'); }
418
503
 
419
- getErrHandling() { return this.#errHandling; }
504
+ if (resultCount < 0 || resultCount > 1) { throw new Error('invalid/unsupported result count'); }
420
505
 
421
- hasCallback() { return this.#callbackFn !== null; }
506
+ const callbackFnName = 'callback_' + callbackIdx;
507
+ const callbackFn = getCallbackFn();
508
+ preparedTask.setCallbackFn(callbackFn, callbackFnName);
509
+ preparedTask.setPostReturnFn(getPostReturnFn());
422
510
 
423
- setReturnMemoryIdx(idx) { this.#memoryIdx = idx; }
424
- getReturnMemoryIdx() { return this.#memoryIdx; }
511
+ const subtask = preparedTask.getParentSubtask();
425
512
 
426
- setReturnLowerFns(fns) { this.#returnLowerFns = fns; }
427
- getReturnLowerFns() { return this.#returnLowerFns; }
513
+ if (resultCount < 0 || resultCount > 1) { throw new Error(`unsupported result count [${ resultCount }]`); }
428
514
 
429
- setParentSubtask(subtask) {
430
- if (!subtask || !(subtask instanceof AsyncSubtask)) { return }
431
- if (this.#parentSubtask) { throw new Error('parent subtask can only be set once'); }
432
- this.#parentSubtask = subtask;
515
+ const params = preparedTask.getCalleeParams();
516
+ if (paramCount !== params.length) {
517
+ throw new Error(`unexpected callee param count [${ params.length }], _asyncStartCall invocation expected [${ paramCount }]`);
433
518
  }
434
519
 
435
- getParentSubtask() { return this.#parentSubtask; }
520
+ subtask.setOnProgressFn(() => {
521
+ subtask.setPendingEventFn(() => {
522
+ if (subtask.resolved()) { subtask.deliverResolve(); }
523
+ return {
524
+ code: ASYNC_EVENT_CODE.SUBTASK,
525
+ index: rep,
526
+ result: subtask.getStateNumber(),
527
+ }
528
+ });
529
+ });
436
530
 
437
- // TODO(threads): this is very inefficient, we can pass along a root task,
438
- // and ideally do not need this once thread support is in place
439
- getRootTask() {
440
- let currentSubtask = this.getParentSubtask();
441
- let task = this;
442
- while (currentSubtask) {
443
- task = currentSubtask.getParentTask();
444
- currentSubtask = task.getParentSubtask();
445
- }
446
- return task;
531
+ const subtaskState = subtask.getStateNumber();
532
+ if (subtaskState < 0 || subtaskState > 2**5) {
533
+ throw new Error('invalid subtask state, out of valid range');
447
534
  }
448
535
 
449
- setPostReturnFn(f) {
450
- if (!f) { return; }
451
- if (this.#postReturnFn) { throw new Error('postReturn fn can only be set once'); }
452
- this.#postReturnFn = f;
453
- }
454
-
455
- setCallbackFn(f, name) {
456
- if (!f) { return; }
457
- if (this.#callbackFn) { throw new Error('callback fn can only be set once'); }
458
- this.#callbackFn = f;
459
- this.#callbackFnName = name;
460
- }
461
-
462
- getCallbackFnName() {
463
- if (!this.#callbackFnName) { return undefined; }
464
- return this.#callbackFnName;
465
- }
466
-
467
- runCallbackFn(...args) {
468
- if (!this.#callbackFn) { throw new Error('on callback function has been set for task'); }
469
- return this.#callbackFn.apply(null, args);
470
- }
536
+ const callerComponentState = getOrCreateAsyncState(subtask.componentIdx());
537
+ const rep = callerComponentState.subtasks.insert(subtask);
538
+ subtask.setRep(rep);
471
539
 
472
- getCalleeParams() {
473
- if (!this.#getCalleeParamsFn) { throw new Error('missing/invalid getCalleeParamsFn'); }
474
- return this.#getCalleeParamsFn();
475
- }
540
+ const calleeComponentState = getOrCreateAsyncState(preparedTask.componentIdx());
541
+ const calleeBackpressure = calleeComponentState.hasBackpressure();
476
542
 
477
- mayEnter(task) {
478
- const cstate = getOrCreateAsyncState(this.#componentIdx);
479
- if (cstate.hasBackpressure()) {
480
- _debugLog('[AsyncTask#mayEnter()] disallowed due to backpressure', { taskID: this.#id });
481
- return false;
543
+ // Set up a handler on subtask completion to lower results from the call into the caller's memory region.
544
+ //
545
+ // NOTE: during fused guest->guest calls this handler is triggered, but does not actually perform
546
+ // lowering manually, as fused modules provider helper functions that can
547
+ subtask.registerOnResolveHandler((res) => {
548
+ _debugLog('[_asyncStartCall()] handling subtask result', { res, subtaskID: subtask.id() });
549
+ let subtaskCallMeta = subtask.getCallMetadata();
550
+
551
+ // NOTE: in the case of guest -> guest async calls, there may be no memory/realloc present,
552
+ // as the host will intermediate the value storage/movement between calls.
553
+ //
554
+ // We can simply take the value and lower it as a parameter
555
+ if (subtaskCallMeta.memory || subtaskCallMeta.realloc) {
556
+ throw new Error("call metadata unexpectedly contains memory/realloc for guest->guest call");
482
557
  }
483
- if (!cstate.callingSyncImport()) {
484
- _debugLog('[AsyncTask#mayEnter()] disallowed due to sync import call', { taskID: this.#id });
485
- return false;
558
+
559
+ const callerTask = subtask.getParentTask();
560
+ const calleeTask = preparedTask;
561
+ const callerMemoryIdx = callerTask.getReturnMemoryIdx();
562
+ const callerComponentIdx = callerTask.componentIdx();
563
+
564
+ // If a helper function was provided we are likely in a fused guest->guest call,
565
+ // and the result will be delivered (lift/lowered) via helper function
566
+ if (subtaskCallMeta.returnFn) {
567
+ _debugLog('[_asyncStartCall()] return function present while ahndling subtask result, returning early (skipping lower)');
568
+ return;
486
569
  }
487
- const callingSyncExportWithSyncPending = cstate.callingSyncExport && !task.isAsync;
488
- if (!callingSyncExportWithSyncPending) {
489
- _debugLog('[AsyncTask#mayEnter()] disallowed due to sync export w/ sync pending', { taskID: this.#id });
490
- return false;
570
+
571
+ // If there is no where to lower the results, exit early
572
+ if (!subtaskCallMeta.resultPtr) {
573
+ _debugLog('[_asyncStartCall()] no result ptr during subtask result handling, returning early (skipping lower)');
574
+ return;
491
575
  }
492
- return true;
493
- }
494
-
495
- async enter() {
496
- _debugLog('[AsyncTask#enter()] args', { taskID: this.#id });
497
- const cstate = getOrCreateAsyncState(this.#componentIdx);
498
576
 
499
- if (this.isSync()) { return true; }
577
+ let callerMemory;
578
+ if (callerMemoryIdx) {
579
+ callerMemory = GlobalComponentMemories.getMemory(callerComponentIdx, callerMemoryIdx);
580
+ } else {
581
+ const callerMemories = GlobalComponentMemories.getMemoriesForComponentIdx(callerComponentIdx);
582
+ if (callerMemories.length != 1) { throw new Error(`unsupported amount of caller memories`); }
583
+ callerMemory = callerMemories[0];
584
+ }
500
585
 
501
- if (cstate.hasBackpressure()) {
502
- cstate.addBackpressureWaiter();
503
-
504
- const result = await this.waitUntil({
505
- readyFn: () => !cstate.hasBackpressure(),
506
- cancellable: true,
507
- });
508
-
509
- cstate.removeBackpressureWaiter();
510
-
511
- if (result === AsyncTask.BlockResult.CANCELLED) {
512
- this.cancel();
513
- return false;
514
- }
586
+ if (!callerMemory) {
587
+ throw new Error(`missing memory for to guest->guest call result (subtask [${subtask.id()}])`);
515
588
  }
516
589
 
517
- if (this.needsExclusiveLock()) { cstate.exclusiveLock(); }
590
+ const lowerFns = calleeTask.getReturnLowerFns();
591
+ if (!lowerFns || lowerFns.length === 0) {
592
+ throw new Error(`missing result lower metadata for guest->guests call (subtask [${subtask.id()}])`);
593
+ }
518
594
 
519
- return true;
520
- }
595
+ if (lowerFns.length !== 1) {
596
+ throw new Error(`only single result supported for guest->guest calls (subtask [${subtask.id()}])`);
597
+ }
598
+
599
+ lowerFns[0]({
600
+ realloc: undefined,
601
+ memory: callerMemory,
602
+ vals: [res],
603
+ storagePtr: subtaskCallMeta.resultPtr,
604
+ componentIdx: callerComponentIdx
605
+ });
606
+
607
+ });
521
608
 
522
- isRunning() {
523
- return this.#state !== AsyncTask.State.RESOLVED;
609
+ // Build call params
610
+ const subtaskCallMeta = subtask.getCallMetadata();
611
+ let startFnParams = [];
612
+ let calleeParams = [];
613
+ if (subtaskCallMeta.startFn && subtaskCallMeta.resultPtr) {
614
+ // If we're using a fused component start fn and a result pointer is present,
615
+ // then we need to pass the result pointer and other params to the start fn
616
+ startFnParams.push(subtaskCallMeta.resultPtr, ...params);
617
+ } else {
618
+ // if not we need to pass params to the callee instead
619
+ startFnParams.push(...params);
620
+ calleeParams.push(...params);
524
621
  }
525
622
 
526
- async waitUntil(opts) {
527
- const { readyFn, waitableSetRep, cancellable } = opts;
528
- _debugLog('[AsyncTask#waitUntil()] args', { taskID: this.#id, waitableSetRep, cancellable });
529
-
530
- const state = getOrCreateAsyncState(this.#componentIdx);
531
- const wset = state.waitableSets.get(waitableSetRep);
532
-
533
- let event;
534
-
535
- wset.incrementNumWaiting();
536
-
537
- const keepGoing = await this.suspendUntil({
538
- readyFn: () => {
539
- const hasPendingEvent = wset.hasPendingEvent();
540
- return readyFn() && hasPendingEvent;
541
- },
542
- cancellable,
623
+ preparedTask.registerOnResolveHandler((res) => {
624
+ _debugLog('[_asyncStartCall()] signaling subtask completion due to task completion', {
625
+ childTaskID: preparedTask.id(),
626
+ subtaskID: subtask.id(),
627
+ parentTaskID: subtask.getParentTask().id(),
628
+ });
629
+ subtask.onResolve(res);
630
+ });
631
+
632
+ // TODO(fix): start fns sometimes produce results, how should they be used?
633
+ // the result should theoretically be used for flat lowering, but fused components do
634
+ // this automatically!
635
+ subtask.onStart({ startFnParams });
636
+
637
+ _debugLog("[_asyncStartCall()] initial call", {
638
+ task: preparedTask.id(),
639
+ subtaskID: subtask.id(),
640
+ calleeFnName: callee.name,
641
+ });
642
+
643
+ const callbackResult = callee.apply(null, calleeParams);
644
+
645
+ _debugLog("[_asyncStartCall()] after initial call", {
646
+ task: preparedTask.id(),
647
+ subtaskID: subtask.id(),
648
+ calleeFnName: callee.name,
649
+ });
650
+
651
+ const doSubtaskResolve = () => {
652
+ subtask.deliverResolve();
653
+ };
654
+
655
+ // If a single call resolved the subtask and there is no backpressure in the guest,
656
+ // we can return immediately
657
+ if (subtask.resolved() && !calleeBackpressure) {
658
+ _debugLog("[_asyncStartCall()] instantly resolved", {
659
+ calleeComponentIdx: preparedTask.componentIdx(),
660
+ task: preparedTask.id(),
661
+ subtaskID: subtask.id(),
662
+ callerComponentIdx: subtask.componentIdx(),
543
663
  });
544
664
 
545
- if (keepGoing) {
546
- event = wset.getPendingEvent();
547
- } else {
548
- event = {
549
- code: ASYNC_EVENT_CODE.TASK_CANCELLED,
550
- index: 0,
551
- result: 0,
552
- };
665
+ // If a fused component return function was specified for the subtask,
666
+ // we've likely already called it during resolution of the task.
667
+ //
668
+ // In this case, we do not want to actually return 2 AKA "RETURNED",
669
+ // but the normal started task state, because the fused component expects to get
670
+ // the waitable + the original subtask state (0 AKA "STARTING")
671
+ //
672
+ if (subtask.getCallMetadata().returnFn) {
673
+ return Number(subtask.waitableRep()) << 4 | subtaskState;
553
674
  }
554
675
 
555
- wset.decrementNumWaiting();
556
-
557
- return event;
676
+ doSubtaskResolve();
677
+ return AsyncSubtask.State.RETURNED;
558
678
  }
559
679
 
560
- async onBlock(awaitable) {
561
- _debugLog('[AsyncTask#onBlock()] args', { taskID: this.#id, awaitable });
562
- if (!(awaitable instanceof Awaitable)) {
563
- throw new Error('invalid awaitable during onBlock');
680
+ // Start the (event) driver loop that will resolve the task
681
+ new Promise(async (resolve, reject) => {
682
+ if (subtask.resolved() && calleeBackpressure) {
683
+ await calleeComponentState.waitForBackpressure();
684
+
685
+ _debugLog("[_asyncStartCall()] instantly resolved after cleared backpressure", {
686
+ calleeComponentIdx: preparedTask.componentIdx(),
687
+ task: preparedTask.id(),
688
+ subtaskID: subtask.id(),
689
+ callerComponentIdx: subtask.componentIdx(),
690
+ });
691
+ return;
564
692
  }
565
693
 
566
- // Build a promise that this task can await on which resolves when it is awoken
567
- const { promise, resolve, reject } = promiseWithResolvers();
568
- this.awaitableResume = () => {
569
- _debugLog('[AsyncTask] resuming after onBlock', { taskID: this.#id });
570
- resolve();
571
- };
572
- this.awaitableCancel = (err) => {
573
- _debugLog('[AsyncTask] rejecting after onBlock', { taskID: this.#id, err });
574
- reject(err);
575
- };
576
-
577
- // Park this task/execution to be handled later
578
- const state = getOrCreateAsyncState(this.#componentIdx);
579
- state.parkTaskOnAwaitable({ awaitable, task: this });
694
+ const started = await preparedTask.enter();
695
+ if (!started) {
696
+ _debugLog('[_asyncStartCall()] task failed early', {
697
+ taskID: preparedTask.id(),
698
+ subtaskID: subtask.id(),
699
+ });
700
+ throw new Error("task failed to start");
701
+ return;
702
+ }
580
703
 
581
- try {
582
- await promise;
583
- return AsyncTask.BlockResult.NOT_CANCELLED;
584
- } catch (err) {
585
- // rejection means task cancellation
586
- return AsyncTask.BlockResult.CANCELLED;
587
- }
588
- }
589
-
590
- async asyncOnBlock(awaitable) {
591
- _debugLog('[AsyncTask#asyncOnBlock()] args', { taskID: this.#id, awaitable });
592
- if (!(awaitable instanceof Awaitable)) {
593
- throw new Error('invalid awaitable during onBlock');
594
- }
595
- // TODO: watch for waitable AND cancellation
596
- // TODO: if it WAS cancelled:
597
- // - return true
598
- // - only once per subtask
599
- // - do not wait on the scheduler
600
- // - control flow should go to the subtask (only once)
601
- // - Once subtask blocks/resolves, reqlinquishControl() will tehn resolve request_cancel_end (without scheduler lock release)
602
- // - control flow goes back to request_cancel
603
- //
604
- // Subtask cancellation should work similarly to an async import call -- runs sync up until
605
- // the subtask blocks or resolves
606
- //
607
- throw new Error('AsyncTask#asyncOnBlock() not yet implemented');
608
- }
609
-
610
- async yieldUntil(opts) {
611
- const { readyFn, cancellable } = opts;
612
- _debugLog('[AsyncTask#yield()] args', { taskID: this.#id, cancellable });
613
-
614
- const keepGoing = await this.suspendUntil({ readyFn, cancellable });
615
- if (!keepGoing) {
616
- return {
617
- code: ASYNC_EVENT_CODE.TASK_CANCELLED,
618
- index: 0,
619
- result: 0,
620
- };
621
- }
622
-
623
- return {
624
- code: ASYNC_EVENT_CODE.NONE,
625
- index: 0,
626
- result: 0,
627
- };
628
- }
629
-
630
- async suspendUntil(opts) {
631
- const { cancellable, readyFn } = opts;
632
- _debugLog('[AsyncTask#suspendUntil()] args', { cancellable });
633
-
634
- const pendingCancelled = this.deliverPendingCancel({ cancellable });
635
- if (pendingCancelled) { return false; }
636
-
637
- const completed = await this.immediateSuspendUntil({ readyFn, cancellable });
638
- return completed;
639
- }
640
-
641
- // TODO(threads): equivalent to thread.suspend_until()
642
- async immediateSuspendUntil(opts) {
643
- const { cancellable, readyFn } = opts;
644
- _debugLog('[AsyncTask#immediateSuspendUntil()] args', { cancellable, readyFn });
704
+ // TODO: retrieve/pass along actual fn name the callback corresponds to
705
+ // (at least something like `<lifted fn name>_callback`)
706
+ const fnName = [
707
+ '<task ',
708
+ subtask.parentTaskID(),
709
+ '/subtask ',
710
+ subtask.id(),
711
+ '/task ',
712
+ preparedTask.id(),
713
+ '>',
714
+ ].join("");
645
715
 
646
- const ready = readyFn();
647
- if (ready && !ASYNC_DETERMINISM && _coinFlip()) {
648
- return true;
716
+ try {
717
+ _debugLog("[_asyncStartCall()] starting driver loop", { fnName, componentIdx: preparedTask.componentIdx(), });
718
+ await _driverLoop({
719
+ componentState: calleeComponentState,
720
+ task: preparedTask,
721
+ fnName,
722
+ isAsync: true,
723
+ callbackResult,
724
+ resolve,
725
+ reject
726
+ });
727
+ } catch (err) {
728
+ _debugLog("[AsyncStartCall] drive loop call failure", { err });
649
729
  }
650
730
 
651
- const cstate = getOrCreateAsyncState(this.#componentIdx);
652
- cstate.addPendingTask(this);
653
-
654
- const keepGoing = await this.immediateSuspend({ cancellable, readyFn });
655
- return keepGoing;
656
- }
657
-
658
- async immediateSuspend(opts) { // NOTE: equivalent to thread.suspend()
659
- // TODO(threads): store readyFn on the thread
660
- const { cancellable, readyFn } = opts;
661
- _debugLog('[AsyncTask#immediateSuspend()] args', { cancellable, readyFn });
662
-
663
- const pendingCancelled = this.deliverPendingCancel({ cancellable });
664
- if (pendingCancelled) { return false; }
665
-
666
- const cstate = getOrCreateAsyncState(this.#componentIdx);
731
+ });
667
732
 
668
- setTimeout(() => cstate.tick(), 0);
669
- const taskWait = await cstate.suspendTask({ task: this, readyFn });
670
- const keepGoing = await taskWait;
671
- return keepGoing;
733
+ return Number(subtask.waitableRep()) << 4 | subtaskState;
672
734
  }
673
735
 
674
- deliverPendingCancel(opts) {
675
- const { cancellable } = opts;
676
- _debugLog('[AsyncTask#deliverPendingCancel()] args', { cancellable });
677
-
678
- if (cancellable && this.#state === AsyncTask.State.PENDING_CANCEL) {
679
- this.#state = Task.State.CANCEL_DELIVERED;
680
- return true;
681
- }
682
-
683
- return false;
736
+ function _syncStartCall(callbackIdx) {
737
+ _debugLog('[_syncStartCall()] args', { callbackIdx });
738
+ throw new Error('synchronous start call not implemented!');
684
739
  }
685
740
 
686
- isCancelled() { return this.cancelled }
741
+ let dv = new DataView(new ArrayBuffer());
742
+ const dataView = mem => dv.buffer === mem.buffer ? dv : dv = new DataView(mem.buffer);
687
743
 
688
- cancel() {
689
- _debugLog('[AsyncTask#cancel()] args', { });
690
- if (!this.taskState() !== AsyncTask.State.CANCEL_DELIVERED) {
691
- throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] invalid task state for cancellation`);
692
- }
693
- if (this.borrowedHandles.length > 0) { throw new Error('task still has borrow handles'); }
694
- this.cancelled = true;
695
- this.onResolve(new Error('cancelled'));
696
- this.#state = AsyncTask.State.RESOLVED;
744
+ const toUint64 = val => BigInt.asUintN(64, BigInt(val));
745
+
746
+ function toUint32(val) {
747
+ return val >>> 0;
697
748
  }
749
+ const TEXT_DECODER_UTF8 = new TextDecoder();
750
+ const TEXT_ENCODER_UTF8 = new TextEncoder();
698
751
 
699
- onResolve(taskValue) {
700
- for (const f of this.#onResolveHandlers) {
701
- try {
702
- f(taskValue);
703
- } catch (err) {
704
- console.error("error during task resolve handler", err);
705
- throw err;
706
- }
707
- }
708
-
709
- if (this.#postReturnFn) {
710
- _debugLog('[AsyncTask#onResolve()] running post return ', {
711
- componentIdx: this.#componentIdx,
712
- taskID: this.#id,
713
- });
714
- this.#postReturnFn();
752
+ function _utf8AllocateAndEncode(s, realloc, memory) {
753
+ if (typeof s !== 'string') {
754
+ throw new TypeError('expected a string, received [' + typeof s + ']');
715
755
  }
756
+ if (s.length === 0) { return { ptr: 1, len: 0 }; }
757
+ let buf = TEXT_ENCODER_UTF8.encode(s);
758
+ let ptr = realloc(0, 0, 1, buf.length);
759
+ new Uint8Array(memory.buffer).set(buf, ptr);
760
+ return { ptr, len: buf.length, codepoints: [...s].length };
716
761
  }
717
762
 
718
- registerOnResolveHandler(f) {
719
- this.#onResolveHandlers.push(f);
720
- }
721
763
 
722
- resolve(results) {
723
- _debugLog('[AsyncTask#resolve()] args', {
724
- results,
725
- componentIdx: this.#componentIdx,
726
- taskID: this.#id,
727
- });
728
-
729
- if (this.#state === AsyncTask.State.RESOLVED) {
730
- throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] is already resolved (did you forget to wait for an import?)`);
731
- }
732
- if (this.borrowedHandles.length > 0) { throw new Error('task still has borrow handles'); }
733
- switch (results.length) {
734
- case 0:
735
- this.onResolve(undefined);
736
- break;
737
- case 1:
738
- this.onResolve(results[0]);
739
- break;
740
- default:
741
- throw new Error('unexpected number of results');
764
+ const T_FLAG = 1 << 30;
765
+
766
+ function rscTableCreateOwn(table, rep) {
767
+ const free = table[0] & ~T_FLAG;
768
+ if (free === 0) {
769
+ table.push(0);
770
+ table.push(rep | T_FLAG);
771
+ return (table.length >> 1) - 1;
742
772
  }
743
- this.#state = AsyncTask.State.RESOLVED;
773
+ table[0] = table[free << 1];
774
+ table[free << 1] = 0;
775
+ table[(free << 1) + 1] = rep | T_FLAG;
776
+ return free;
744
777
  }
745
778
 
746
- exit() {
747
- _debugLog('[AsyncTask#exit()] args', { });
748
-
749
- // TODO: ensure there is only one task at a time (scheduler.lock() functionality)
750
- if (this.#state !== AsyncTask.State.RESOLVED) {
751
- // TODO(fix): only fused, manually specified post returns seem to break this invariant,
752
- // as the TaskReturn trampoline is not activated it seems.
753
- //
754
- // see: test/p3/ported/wasmtime/component-async/post-return.js
755
- //
756
- // We *should* be able to upgrade this to be more strict and throw at some point,
757
- // which may involve rewriting the upstream test to surface task return manually somehow.
758
- //
759
- //throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] exited without resolution`);
760
- _debugLog('[AsyncTask#exit()] task exited without resolution', {
761
- componentIdx: this.#componentIdx,
762
- taskID: this.#id,
763
- subtask: this.getParentSubtask(),
764
- subtaskID: this.getParentSubtask()?.id(),
765
- });
766
- this.#state = AsyncTask.State.RESOLVED;
767
- }
768
-
769
- if (this.borrowedHandles > 0) {
770
- throw new Error('task [${this.#id}] exited without clearing borrowed handles');
771
- }
772
-
773
- const state = getOrCreateAsyncState(this.#componentIdx);
774
- if (!state) { throw new Error('missing async state for component [' + this.#componentIdx + ']'); }
775
- if (!this.#isAsync && !state.inSyncExportCall) {
776
- throw new Error('sync task must be run from components known to be in a sync export call');
777
- }
778
- state.inSyncExportCall = false;
779
-
780
- if (this.needsExclusiveLock() && !state.isExclusivelyLocked()) {
781
- throw new Error('task [' + this.#id + '] exit: component [' + this.#componentIdx + '] should have been exclusively locked');
779
+ function rscTableRemove(table, handle) {
780
+ const scope = table[handle << 1];
781
+ const val = table[(handle << 1) + 1];
782
+ const own = (val & T_FLAG) !== 0;
783
+ const rep = val & ~T_FLAG;
784
+ if (val === 0 || (scope & T_FLAG) !== 0) {
785
+ throw new TypeError("Invalid handle");
782
786
  }
783
-
784
- state.exclusiveRelease();
787
+ table[handle << 1] = table[0] | T_FLAG;
788
+ table[0] = handle | T_FLAG;
789
+ return { rep, scope, own };
785
790
  }
786
791
 
787
- needsExclusiveLock() { return this.#needsExclusiveLock; }
792
+ let curResourceBorrows = [];
788
793
 
789
- createSubtask(args) {
790
- _debugLog('[AsyncTask#createSubtask()] args', args);
791
- const { componentIdx, childTask, callMetadata } = args;
792
- const newSubtask = new AsyncSubtask({
793
- componentIdx,
794
- childTask,
795
- parentTask: this,
796
- callMetadata,
797
- });
798
- this.#subtasks.push(newSubtask);
799
- return newSubtask;
800
- }
801
-
802
- getLatestSubtask() { return this.#subtasks.at(-1); }
803
-
804
- currentSubtask() {
805
- _debugLog('[AsyncTask#currentSubtask()]');
806
- if (this.#subtasks.length === 0) { return undefined; }
807
- return this.#subtasks.at(-1);
808
- }
809
-
810
- endCurrentSubtask() {
811
- _debugLog('[AsyncTask#endCurrentSubtask()]');
812
- if (this.#subtasks.length === 0) { throw new Error('cannot end current subtask: no current subtask'); }
813
- const subtask = this.#subtasks.pop();
814
- subtask.drop();
815
- return subtask;
816
- }
817
- }
818
-
819
- function unpackCallbackResult(result) {
820
- _debugLog('[unpackCallbackResult()] args', { result });
821
- if (!(_typeCheckValidI32(result))) { throw new Error('invalid callback return value [' + result + '], not a valid i32'); }
822
- const eventCode = result & 0xF;
823
- if (eventCode < 0 || eventCode > 3) {
824
- throw new Error('invalid async return value [' + eventCode + '], outside callback code range');
794
+ function getCurrentTask(componentIdx) {
795
+ if (componentIdx === undefined || componentIdx === null) {
796
+ throw new Error('missing/invalid component instance index [' + componentIdx + '] while getting current task');
825
797
  }
826
- if (result < 0 || result >= 2**32) { throw new Error('invalid callback result'); }
827
- // TODO: table max length check?
828
- const waitableSetRep = result >> 4;
829
- return [eventCode, waitableSetRep];
798
+ const tasks = ASYNC_TASKS_BY_COMPONENT_IDX.get(componentIdx);
799
+ if (tasks === undefined) { return undefined; }
800
+ if (tasks.length === 0) { return undefined; }
801
+ return tasks[tasks.length - 1];
830
802
  }
831
803
 
832
- function _lowerImport(args, exportFn) {
833
- const params = [...arguments].slice(2);
834
- _debugLog('[_lowerImport()] args', { args, params, exportFn });
804
+ function createNewCurrentTask(args) {
805
+ _debugLog('[createNewCurrentTask()] args', args);
835
806
  const {
836
- functionIdx,
837
807
  componentIdx,
838
808
  isAsync,
839
- paramLiftFns,
840
- resultLowerFns,
841
- metadata,
842
- memoryIdx,
843
- getMemoryFn,
844
- getReallocFn,
809
+ entryFnName,
810
+ parentSubtaskID,
811
+ callbackFnName,
812
+ getCallbackFn,
813
+ getParamsFn,
814
+ stringEncoding,
815
+ errHandling,
816
+ getCalleeParamsFn,
817
+ resultPtr,
818
+ callingWasmExport,
845
819
  } = args;
820
+ if (componentIdx === undefined || componentIdx === null) {
821
+ throw new Error('missing/invalid component instance index while starting task');
822
+ }
823
+ const taskMetas = ASYNC_TASKS_BY_COMPONENT_IDX.get(componentIdx);
824
+ const callbackFn = getCallbackFn ? getCallbackFn() : null;
846
825
 
847
- const parentTaskMeta = getCurrentTask(componentIdx);
848
- const parentTask = parentTaskMeta?.task;
849
- if (!parentTask) { throw new Error('missing parent task during lower of import'); }
850
-
851
- const cstate = getOrCreateAsyncState(componentIdx);
852
-
853
- const subtask = parentTask.createSubtask({
826
+ const newTask = new AsyncTask({
854
827
  componentIdx,
855
- parentTask,
856
- callMetadata: {
857
- memoryIdx,
858
- memory: getMemoryFn(),
859
- realloc: getReallocFn(),
860
- resultPtr: params[0],
861
- }
828
+ isAsync,
829
+ entryFnName,
830
+ callbackFn,
831
+ callbackFnName,
832
+ stringEncoding,
833
+ getCalleeParamsFn,
834
+ resultPtr,
835
+ errHandling,
862
836
  });
863
- parentTask.setReturnMemoryIdx(memoryIdx);
864
-
865
- const rep = cstate.subtasks.insert(subtask);
866
- subtask.setRep(rep);
867
837
 
868
- subtask.setOnProgressFn(() => {
869
- subtask.setPendingEventFn(() => {
870
- if (subtask.resolved()) { subtask.deliverResolve(); }
871
- return {
872
- code: ASYNC_EVENT_CODE.SUBTASK,
873
- index: rep,
874
- result: subtask.getStateNumber(),
875
- }
876
- });
877
- });
838
+ const newTaskID = newTask.id();
839
+ const newTaskMeta = { id: newTaskID, componentIdx, task: newTask };
878
840
 
879
- // Set up a handler on subtask completion to lower results from the call into the caller's memory region.
880
- subtask.registerOnResolveHandler((res) => {
881
- _debugLog('[_lowerImport()] handling subtask result', { res, subtaskID: subtask.id() });
882
- const { memory, resultPtr, realloc } = subtask.getCallMetadata();
883
- resultLowerFns[0]({ componentIdx, memory, realloc, vals: [res], storagePtr: resultPtr });
884
- });
841
+ ASYNC_CURRENT_TASK_IDS.push(newTaskID);
842
+ ASYNC_CURRENT_COMPONENT_IDXS.push(componentIdx);
885
843
 
886
- const subtaskState = subtask.getStateNumber();
887
- if (subtaskState < 0 || subtaskState > 2**5) {
888
- throw new Error('invalid subtask state, out of valid range');
844
+ if (!taskMetas) {
845
+ ASYNC_TASKS_BY_COMPONENT_IDX.set(componentIdx, [newTaskMeta]);
846
+ } else {
847
+ taskMetas.push(newTaskMeta);
889
848
  }
890
849
 
891
- // NOTE: we must wait a bit before calling the export function,
892
- // to ensure the subtask state is not modified before the lower call return
893
- //
894
- // TODO: we should trigger via subtask state changing, rather than a static wait?
895
- setTimeout(async () => {
896
- try {
897
- _debugLog('[_lowerImport()] calling lowered import', { exportFn, params });
898
- exportFn.apply(null, params);
899
-
900
- const task = subtask.getChildTask();
901
- task.registerOnResolveHandler((res) => {
902
- _debugLog('[_lowerImport()] cascading subtask completion', {
903
- childTaskID: task.id(),
904
- subtaskID: subtask.id(),
905
- parentTaskID: parentTask.id(),
906
- });
907
-
908
- subtask.onResolve(res);
909
-
910
- cstate.tick();
911
- });
912
- } catch (err) {
913
- console.error("post-lower import fn error:", err);
914
- throw err;
915
- }
916
- }, 100);
917
-
918
- return Number(subtask.waitableRep()) << 4 | subtaskState;
850
+ return [newTask, newTaskID];
919
851
  }
920
852
 
921
- function _liftFlatU8(ctx) {
922
- _debugLog('[_liftFlatU8()] args', { ctx });
853
+ function endCurrentTask(componentIdx, taskID) {
854
+ componentIdx ??= ASYNC_CURRENT_COMPONENT_IDXS.at(-1);
855
+ taskID ??= ASYNC_CURRENT_TASK_IDS.at(-1);
856
+ _debugLog('[endCurrentTask()] args', { componentIdx, taskID });
923
857
 
924
- let val;
925
- if (ctx.useDirectParams) {
926
- if (ctx.params.length === 0) { throw new Error('expected at least a single i32 argument'); }
927
- val = ctx.params[0];
928
- ctx.params = ctx.params.slice(1);
929
- } else {
930
- if (ctx.storageLen < ctx.storagePtr + 1) { throw new Error('not enough storage remaining for lift'); }
931
- val = new DataView(ctx.memory.buffer).getUint8(ctx.storagePtr);
932
- ctx.storagePtr += 1;
933
- ctx.storageLen -= 1;
858
+ if (componentIdx === undefined || componentIdx === null) {
859
+ throw new Error('missing/invalid component instance index while ending current task');
934
860
  }
935
861
 
936
- return [val, ctx];
937
- }
938
-
939
- function _liftFlatU16(ctx) {
940
- _debugLog('[_liftFlatU16()] args', { ctx });
862
+ const tasks = ASYNC_TASKS_BY_COMPONENT_IDX.get(componentIdx);
863
+ if (!tasks || !Array.isArray(tasks)) {
864
+ throw new Error('missing/invalid tasks for component instance while ending task');
865
+ }
866
+ if (tasks.length == 0) {
867
+ throw new Error('no current task(s) for component instance while ending task');
868
+ }
941
869
 
942
- let val;
943
- if (ctx.useDirectParams) {
944
- if (params.length === 0) { throw new Error('expected at least a single i32 argument'); }
945
- val = ctx.params[0];
946
- ctx.params = ctx.params.slice(1);
947
- } else {
948
- if (ctx.storageLen < ctx.storagePtr + 2) { throw new Error('not enough storage remaining for lift'); }
949
- val = new DataView(ctx.memory.buffer).getUint16(ctx.storagePtr);
950
- ctx.storagePtr += 2;
951
- ctx.storageLen -= 2;
870
+ if (taskID) {
871
+ const last = tasks[tasks.length - 1];
872
+ if (last.id !== taskID) {
873
+ // throw new Error('current task does not match expected task ID');
874
+ return;
875
+ }
952
876
  }
953
877
 
954
- return [val, ctx];
878
+ ASYNC_CURRENT_TASK_IDS.pop();
879
+ ASYNC_CURRENT_COMPONENT_IDXS.pop();
880
+
881
+ const taskMeta = tasks.pop();
882
+ return taskMeta.task;
955
883
  }
884
+ const ASYNC_TASKS_BY_COMPONENT_IDX = new Map();
956
885
 
957
- function _liftFlatU32(ctx) {
958
- _debugLog('[_liftFlatU32()] args', { ctx });
886
+ class AsyncTask {
887
+ static _ID = 0n;
959
888
 
960
- let val;
961
- if (ctx.useDirectParams) {
962
- if (ctx.params.length === 0) { throw new Error('expected at least a single i34 argument'); }
963
- val = ctx.params[0];
964
- ctx.params = ctx.params.slice(1);
965
- } else {
966
- if (ctx.storageLen < ctx.storagePtr + 4) { throw new Error('not enough storage remaining for lift'); }
967
- val = new DataView(ctx.memory.buffer).getUint32(ctx.storagePtr);
968
- ctx.storagePtr += 4;
969
- ctx.storageLen -= 4;
889
+ static State = {
890
+ INITIAL: 'initial',
891
+ CANCELLED: 'cancelled',
892
+ CANCEL_PENDING: 'cancel-pending',
893
+ CANCEL_DELIVERED: 'cancel-delivered',
894
+ RESOLVED: 'resolved',
970
895
  }
971
896
 
972
- return [val, ctx];
973
- }
974
-
975
- function _liftFlatU64(ctx) {
976
- _debugLog('[_liftFlatU64()] args', { ctx });
977
-
978
- let val;
979
- if (ctx.useDirectParams) {
980
- if (ctx.params.length === 0) { throw new Error('expected at least one single i64 argument'); }
981
- if (typeof ctx.params[0] !== 'bigint') { throw new Error('expected bigint'); }
982
- val = ctx.params[0];
983
- ctx.params = ctx.params.slice(1);
984
- } else {
985
- if (ctx.storageLen < ctx.storagePtr + 8) { throw new Error('not enough storage remaining for lift'); }
986
- val = new DataView(ctx.memory.buffer).getUint64(ctx.storagePtr);
987
- ctx.storagePtr += 8;
988
- ctx.storageLen -= 8;
897
+ static BlockResult = {
898
+ CANCELLED: 'block.cancelled',
899
+ NOT_CANCELLED: 'block.not-cancelled',
989
900
  }
990
901
 
991
- return [val, ctx];
992
- }
993
-
994
- function _liftFlatStringUTF8(ctx) {
995
- _debugLog('[_liftFlatStringUTF8()] args', { ctx });
902
+ #id;
903
+ #componentIdx;
904
+ #state;
905
+ #isAsync;
906
+ #entryFnName = null;
907
+ #subtasks = [];
996
908
 
997
- let val;
998
- if (ctx.useDirectParams) {
999
- if (ctx.params.length < 2) { throw new Error('expected at least two u32 arguments'); }
1000
- const offset = ctx.params[0];
1001
- if (!Number.isSafeInteger(offset)) { throw new Error('invalid offset'); }
1002
- const len = ctx.params[1];
1003
- if (!Number.isSafeInteger(len)) { throw new Error('invalid len'); }
1004
- val = TEXT_DECODER_UTF8.decode(new DataView(ctx.memory.buffer, offset, len));
1005
- ctx.params = ctx.params.slice(2);
1006
- } else {
1007
- const start = new DataView(ctx.memory.buffer).getUint32(ctx.storagePtr, params[0], true);
1008
- const codeUnits = new DataView(memory.buffer).getUint32(ctx.storagePtr, params[0] + 4, true);
1009
- val = TEXT_DECODER_UTF8.decode(new Uint8Array(ctx.memory.buffer, start, codeUnits));
1010
- ctx.storagePtr += codeUnits;
1011
- ctx.storageLen -= codeUnits;
1012
- }
909
+ #onResolveHandlers = [];
910
+ #completionPromise = null;
1013
911
 
1014
- return [val, ctx];
1015
- }
1016
-
1017
- function _liftFlatVariant(casesAndLiftFns) {
1018
- return function _liftFlatVariantInner(ctx) {
1019
- _debugLog('[_liftFlatVariant()] args', { ctx });
1020
-
1021
- const origUseParams = ctx.useDirectParams;
912
+ #memoryIdx = null;
913
+
914
+ #callbackFn = null;
915
+ #callbackFnName = null;
916
+
917
+ #postReturnFn = null;
918
+
919
+ #getCalleeParamsFn = null;
920
+
921
+ #stringEncoding = null;
922
+
923
+ #parentSubtask = null;
924
+
925
+ #needsExclusiveLock = false;
926
+
927
+ #errHandling;
928
+
929
+ #backpressurePromise;
930
+ #backpressureWaiters = 0n;
931
+
932
+ #returnLowerFns = null;
933
+
934
+ cancelled = false;
935
+ requested = false;
936
+ alwaysTaskReturn = false;
937
+
938
+ returnCalls = 0;
939
+ storage = [0, 0];
940
+ borrowedHandles = {};
941
+
942
+ awaitableResume = null;
943
+ awaitableCancel = null;
944
+
945
+ constructor(opts) {
946
+ this.#id = ++AsyncTask._ID;
1022
947
 
1023
- let caseIdx;
1024
- if (casesAndLiftFns.length < 256) {
1025
- let discriminantByteLen = 1;
1026
- const [idx, newCtx] = _liftFlatU8(ctx);
1027
- caseIdx = idx;
1028
- ctx = newCtx;
1029
- } else if (casesAndLiftFns.length > 256 && discriminantByteLen < 65536) {
1030
- discriminantByteLen = 2;
1031
- const [idx, newCtx] = _liftFlatU16(ctx);
1032
- caseIdx = idx;
1033
- ctx = newCtx;
1034
- } else if (casesAndLiftFns.length > 65536 && discriminantByteLen < 4_294_967_296) {
1035
- discriminantByteLen = 4;
1036
- const [idx, newCtx] = _liftFlatU32(ctx);
1037
- caseIdx = idx;
1038
- ctx = newCtx;
1039
- } else {
1040
- throw new Error('unsupported number of cases [' + casesAndLIftFns.legnth + ']');
948
+ if (opts?.componentIdx === undefined) {
949
+ throw new TypeError('missing component id during task creation');
1041
950
  }
951
+ this.#componentIdx = opts.componentIdx;
1042
952
 
1043
- const [ tag, liftFn, size32, alignment32 ] = casesAndLiftFns[caseIdx];
953
+ this.#state = AsyncTask.State.INITIAL;
954
+ this.#isAsync = opts?.isAsync ?? false;
955
+ this.#entryFnName = opts.entryFnName;
1044
956
 
1045
- let val;
1046
- if (liftFn === null) {
1047
- val = { tag };
1048
- return [val, ctx];
1049
- }
957
+ const {
958
+ promise: completionPromise,
959
+ resolve: resolveCompletionPromise,
960
+ reject: rejectCompletionPromise,
961
+ } = promiseWithResolvers();
962
+ this.#completionPromise = completionPromise;
1050
963
 
1051
- const [newVal, newCtx] = liftFn(ctx);
1052
- ctx = newCtx;
1053
- val = { tag, val: newVal };
964
+ this.#onResolveHandlers.push((results) => {
965
+ resolveCompletionPromise(results);
966
+ })
1054
967
 
1055
- return [val, ctx];
968
+ if (opts.callbackFn) { this.#callbackFn = opts.callbackFn; }
969
+ if (opts.callbackFnName) { this.#callbackFnName = opts.callbackFnName; }
970
+
971
+ if (opts.getCalleeParamsFn) { this.#getCalleeParamsFn = opts.getCalleeParamsFn; }
972
+
973
+ if (opts.stringEncoding) { this.#stringEncoding = opts.stringEncoding; }
974
+
975
+ if (opts.parentSubtask) { this.#parentSubtask = opts.parentSubtask; }
976
+
977
+ this.#needsExclusiveLock = this.isSync() || !this.hasCallback();
978
+
979
+ if (opts.errHandling) { this.#errHandling = opts.errHandling; }
1056
980
  }
1057
- }
1058
-
1059
- function _liftFlatList(elemLiftFn, alignment32, knownLen) {
1060
- function _liftFlatListInner(ctx) {
1061
- _debugLog('[_liftFlatList()] args', { ctx });
981
+
982
+ taskState() { return this.#state; }
983
+ id() { return this.#id; }
984
+ componentIdx() { return this.#componentIdx; }
985
+ isAsync() { return this.#isAsync; }
986
+ entryFnName() { return this.#entryFnName; }
987
+ completionPromise() { return this.#completionPromise; }
988
+
989
+ isAsync() { return this.#isAsync; }
990
+ isSync() { return !this.isAsync(); }
991
+
992
+ getErrHandling() { return this.#errHandling; }
993
+
994
+ hasCallback() { return this.#callbackFn !== null; }
995
+
996
+ setReturnMemoryIdx(idx) { this.#memoryIdx = idx; }
997
+ getReturnMemoryIdx() { return this.#memoryIdx; }
998
+
999
+ setReturnLowerFns(fns) { this.#returnLowerFns = fns; }
1000
+ getReturnLowerFns() { return this.#returnLowerFns; }
1001
+
1002
+ setParentSubtask(subtask) {
1003
+ if (!subtask || !(subtask instanceof AsyncSubtask)) { return }
1004
+ if (this.#parentSubtask) { throw new Error('parent subtask can only be set once'); }
1005
+ this.#parentSubtask = subtask;
1006
+ }
1007
+
1008
+ getParentSubtask() { return this.#parentSubtask; }
1009
+
1010
+ // TODO(threads): this is very inefficient, we can pass along a root task,
1011
+ // and ideally do not need this once thread support is in place
1012
+ getRootTask() {
1013
+ let currentSubtask = this.getParentSubtask();
1014
+ let task = this;
1015
+ while (currentSubtask) {
1016
+ task = currentSubtask.getParentTask();
1017
+ currentSubtask = task.getParentSubtask();
1018
+ }
1019
+ return task;
1020
+ }
1021
+
1022
+ setPostReturnFn(f) {
1023
+ if (!f) { return; }
1024
+ if (this.#postReturnFn) { throw new Error('postReturn fn can only be set once'); }
1025
+ this.#postReturnFn = f;
1026
+ }
1027
+
1028
+ setCallbackFn(f, name) {
1029
+ if (!f) { return; }
1030
+ if (this.#callbackFn) { throw new Error('callback fn can only be set once'); }
1031
+ this.#callbackFn = f;
1032
+ this.#callbackFnName = name;
1033
+ }
1034
+
1035
+ getCallbackFnName() {
1036
+ if (!this.#callbackFnName) { return undefined; }
1037
+ return this.#callbackFnName;
1038
+ }
1039
+
1040
+ runCallbackFn(...args) {
1041
+ if (!this.#callbackFn) { throw new Error('on callback function has been set for task'); }
1042
+ return this.#callbackFn.apply(null, args);
1043
+ }
1044
+
1045
+ getCalleeParams() {
1046
+ if (!this.#getCalleeParamsFn) { throw new Error('missing/invalid getCalleeParamsFn'); }
1047
+ return this.#getCalleeParamsFn();
1048
+ }
1049
+
1050
+ mayEnter(task) {
1051
+ const cstate = getOrCreateAsyncState(this.#componentIdx);
1052
+ if (cstate.hasBackpressure()) {
1053
+ _debugLog('[AsyncTask#mayEnter()] disallowed due to backpressure', { taskID: this.#id });
1054
+ return false;
1055
+ }
1056
+ if (!cstate.callingSyncImport()) {
1057
+ _debugLog('[AsyncTask#mayEnter()] disallowed due to sync import call', { taskID: this.#id });
1058
+ return false;
1059
+ }
1060
+ const callingSyncExportWithSyncPending = cstate.callingSyncExport && !task.isAsync;
1061
+ if (!callingSyncExportWithSyncPending) {
1062
+ _debugLog('[AsyncTask#mayEnter()] disallowed due to sync export w/ sync pending', { taskID: this.#id });
1063
+ return false;
1064
+ }
1065
+ return true;
1066
+ }
1067
+
1068
+ async enter() {
1069
+ _debugLog('[AsyncTask#enter()] args', { taskID: this.#id });
1070
+ const cstate = getOrCreateAsyncState(this.#componentIdx);
1062
1071
 
1063
- let metaPtr;
1064
- let dataPtr;
1065
- let len;
1066
- if (ctx.useDirectParams) {
1067
- if (knownLen) {
1068
- dataPtr = _liftFlatU32(ctx);
1069
- } else {
1070
- metaPtr = _liftFlatU32(ctx);
1072
+ if (this.isSync()) { return true; }
1073
+
1074
+ if (cstate.hasBackpressure()) {
1075
+ cstate.addBackpressureWaiter();
1076
+
1077
+ const result = await this.waitUntil({
1078
+ readyFn: () => !cstate.hasBackpressure(),
1079
+ cancellable: true,
1080
+ });
1081
+
1082
+ cstate.removeBackpressureWaiter();
1083
+
1084
+ if (result === AsyncTask.BlockResult.CANCELLED) {
1085
+ this.cancel();
1086
+ return false;
1071
1087
  }
1088
+ }
1089
+
1090
+ if (this.needsExclusiveLock()) { cstate.exclusiveLock(); }
1091
+
1092
+ return true;
1093
+ }
1094
+
1095
+ isRunning() {
1096
+ return this.#state !== AsyncTask.State.RESOLVED;
1097
+ }
1098
+
1099
+ async waitUntil(opts) {
1100
+ const { readyFn, waitableSetRep, cancellable } = opts;
1101
+ _debugLog('[AsyncTask#waitUntil()] args', { taskID: this.#id, waitableSetRep, cancellable });
1102
+
1103
+ const state = getOrCreateAsyncState(this.#componentIdx);
1104
+ const wset = state.waitableSets.get(waitableSetRep);
1105
+
1106
+ let event;
1107
+
1108
+ wset.incrementNumWaiting();
1109
+
1110
+ const keepGoing = await this.suspendUntil({
1111
+ readyFn: () => {
1112
+ const hasPendingEvent = wset.hasPendingEvent();
1113
+ return readyFn() && hasPendingEvent;
1114
+ },
1115
+ cancellable,
1116
+ });
1117
+
1118
+ if (keepGoing) {
1119
+ event = wset.getPendingEvent();
1072
1120
  } else {
1073
- if (knownLen) {
1074
- dataPtr = _liftFlatU32(ctx);
1075
- } else {
1076
- metaPtr = _liftFlatU32(ctx);
1077
- }
1121
+ event = {
1122
+ code: ASYNC_EVENT_CODE.TASK_CANCELLED,
1123
+ index: 0,
1124
+ result: 0,
1125
+ };
1078
1126
  }
1079
1127
 
1080
- if (metaPtr) {
1081
- if (dataPtr !== undefined) { throw new Error('both meta and data pointers should not be set yet'); }
1082
-
1083
- if (ctx.useDirectParams) {
1084
- ctx.useDirectParams = false;
1085
- ctx.storagePtr = metaPtr;
1086
- ctx.storageLen = 8;
1087
-
1088
- dataPtr = _liftFlatU32(ctx);
1089
- len = _liftFlatU32(ctx);
1090
-
1091
- ctx.useDirectParams = true;
1092
- ctx.storagePtr = null;
1093
- ctx.storageLen = null;
1094
- } else {
1095
- dataPtr = _liftFlatU32(ctx);
1096
- len = _liftFlatU32(ctx);
1097
- }
1128
+ wset.decrementNumWaiting();
1129
+
1130
+ return event;
1131
+ }
1132
+
1133
+ async onBlock(awaitable) {
1134
+ _debugLog('[AsyncTask#onBlock()] args', { taskID: this.#id, awaitable });
1135
+ if (!(awaitable instanceof Awaitable)) {
1136
+ throw new Error('invalid awaitable during onBlock');
1098
1137
  }
1099
1138
 
1100
- const val = [];
1101
- for (var i = 0; i < len; i++) {
1102
- ctx.storagePtr = Math.ceil(ctx.storagePtr / alignment32) * alignment32;
1103
- const [res, nextCtx] = elemLiftFn(ctx);
1104
- val.push(res);
1105
- ctx = nextCtx;
1139
+ // Build a promise that this task can await on which resolves when it is awoken
1140
+ const { promise, resolve, reject } = promiseWithResolvers();
1141
+ this.awaitableResume = () => {
1142
+ _debugLog('[AsyncTask] resuming after onBlock', { taskID: this.#id });
1143
+ resolve();
1144
+ };
1145
+ this.awaitableCancel = (err) => {
1146
+ _debugLog('[AsyncTask] rejecting after onBlock', { taskID: this.#id, err });
1147
+ reject(err);
1148
+ };
1149
+
1150
+ // Park this task/execution to be handled later
1151
+ const state = getOrCreateAsyncState(this.#componentIdx);
1152
+ state.parkTaskOnAwaitable({ awaitable, task: this });
1153
+
1154
+ try {
1155
+ await promise;
1156
+ return AsyncTask.BlockResult.NOT_CANCELLED;
1157
+ } catch (err) {
1158
+ // rejection means task cancellation
1159
+ return AsyncTask.BlockResult.CANCELLED;
1160
+ }
1161
+ }
1162
+
1163
+ async asyncOnBlock(awaitable) {
1164
+ _debugLog('[AsyncTask#asyncOnBlock()] args', { taskID: this.#id, awaitable });
1165
+ if (!(awaitable instanceof Awaitable)) {
1166
+ throw new Error('invalid awaitable during onBlock');
1167
+ }
1168
+ // TODO: watch for waitable AND cancellation
1169
+ // TODO: if it WAS cancelled:
1170
+ // - return true
1171
+ // - only once per subtask
1172
+ // - do not wait on the scheduler
1173
+ // - control flow should go to the subtask (only once)
1174
+ // - Once subtask blocks/resolves, reqlinquishControl() will tehn resolve request_cancel_end (without scheduler lock release)
1175
+ // - control flow goes back to request_cancel
1176
+ //
1177
+ // Subtask cancellation should work similarly to an async import call -- runs sync up until
1178
+ // the subtask blocks or resolves
1179
+ //
1180
+ throw new Error('AsyncTask#asyncOnBlock() not yet implemented');
1181
+ }
1182
+
1183
+ async yieldUntil(opts) {
1184
+ const { readyFn, cancellable } = opts;
1185
+ _debugLog('[AsyncTask#yieldUntil()] args', { taskID: this.#id, cancellable });
1186
+
1187
+ const keepGoing = await this.suspendUntil({ readyFn, cancellable });
1188
+ if (!keepGoing) {
1189
+ return {
1190
+ code: ASYNC_EVENT_CODE.TASK_CANCELLED,
1191
+ index: 0,
1192
+ result: 0,
1193
+ };
1106
1194
  }
1107
1195
 
1108
- return [val, ctx];
1196
+ return {
1197
+ code: ASYNC_EVENT_CODE.NONE,
1198
+ index: 0,
1199
+ result: 0,
1200
+ };
1201
+ }
1202
+
1203
+ async suspendUntil(opts) {
1204
+ const { cancellable, readyFn } = opts;
1205
+ _debugLog('[AsyncTask#suspendUntil()] args', { cancellable });
1206
+
1207
+ const pendingCancelled = this.deliverPendingCancel({ cancellable });
1208
+ if (pendingCancelled) { return false; }
1209
+
1210
+ const completed = await this.immediateSuspendUntil({ readyFn, cancellable });
1211
+ return completed;
1212
+ }
1213
+
1214
+ // TODO(threads): equivalent to thread.suspend_until()
1215
+ async immediateSuspendUntil(opts) {
1216
+ const { cancellable, readyFn } = opts;
1217
+ _debugLog('[AsyncTask#immediateSuspendUntil()] args', { cancellable, readyFn });
1218
+
1219
+ const ready = readyFn();
1220
+ if (ready && !ASYNC_DETERMINISM && _coinFlip()) {
1221
+ return true;
1222
+ }
1223
+
1224
+ const cstate = getOrCreateAsyncState(this.#componentIdx);
1225
+ cstate.addPendingTask(this);
1226
+
1227
+ const keepGoing = await this.immediateSuspend({ cancellable, readyFn });
1228
+ return keepGoing;
1229
+ }
1230
+
1231
+ async immediateSuspend(opts) { // NOTE: equivalent to thread.suspend()
1232
+ // TODO(threads): store readyFn on the thread
1233
+ const { cancellable, readyFn } = opts;
1234
+ _debugLog('[AsyncTask#immediateSuspend()] args', { cancellable, readyFn });
1235
+
1236
+ const pendingCancelled = this.deliverPendingCancel({ cancellable });
1237
+ if (pendingCancelled) { return false; }
1238
+
1239
+ const cstate = getOrCreateAsyncState(this.#componentIdx);
1240
+
1241
+ // TODO(fix): update this to tick until there is no more action to take.
1242
+ setTimeout(() => cstate.tick(), 0);
1243
+
1244
+ const taskWait = await cstate.suspendTask({ task: this, readyFn });
1245
+ const keepGoing = await taskWait;
1246
+ return keepGoing;
1247
+ }
1248
+
1249
+ deliverPendingCancel(opts) {
1250
+ const { cancellable } = opts;
1251
+ _debugLog('[AsyncTask#deliverPendingCancel()] args', { cancellable });
1252
+
1253
+ if (cancellable && this.#state === AsyncTask.State.PENDING_CANCEL) {
1254
+ this.#state = Task.State.CANCEL_DELIVERED;
1255
+ return true;
1256
+ }
1257
+
1258
+ return false;
1259
+ }
1260
+
1261
+ isCancelled() { return this.cancelled }
1262
+
1263
+ cancel() {
1264
+ _debugLog('[AsyncTask#cancel()] args', { });
1265
+ if (!this.taskState() !== AsyncTask.State.CANCEL_DELIVERED) {
1266
+ throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] invalid task state for cancellation`);
1267
+ }
1268
+ if (this.borrowedHandles.length > 0) { throw new Error('task still has borrow handles'); }
1269
+ this.cancelled = true;
1270
+ this.onResolve(new Error('cancelled'));
1271
+ this.#state = AsyncTask.State.RESOLVED;
1272
+ }
1273
+
1274
+ onResolve(taskValue) {
1275
+ for (const f of this.#onResolveHandlers) {
1276
+ try {
1277
+ f(taskValue);
1278
+ } catch (err) {
1279
+ console.error("error during task resolve handler", err);
1280
+ throw err;
1281
+ }
1282
+ }
1283
+
1284
+ if (this.#postReturnFn) {
1285
+ _debugLog('[AsyncTask#onResolve()] running post return ', {
1286
+ componentIdx: this.#componentIdx,
1287
+ taskID: this.#id,
1288
+ });
1289
+ this.#postReturnFn();
1109
1290
  }
1110
1291
  }
1111
1292
 
1112
- function _liftFlatFlags(cases) {
1113
- return function _liftFlatFlagsInner(ctx) {
1114
- _debugLog('[_liftFlatFlags()] args', { ctx });
1115
- throw new Error('flat lift for flags not yet implemented!');
1116
- }
1293
+ registerOnResolveHandler(f) {
1294
+ this.#onResolveHandlers.push(f);
1117
1295
  }
1118
1296
 
1119
- function _liftFlatResult(casesAndLiftFns) {
1120
- return function _liftFlatResultInner(ctx) {
1121
- _debugLog('[_liftFlatResult()] args', { ctx });
1122
- return _liftFlatVariant(casesAndLiftFns)(ctx);
1297
+ resolve(results) {
1298
+ _debugLog('[AsyncTask#resolve()] args', {
1299
+ results,
1300
+ componentIdx: this.#componentIdx,
1301
+ taskID: this.#id,
1302
+ });
1303
+
1304
+ if (this.#state === AsyncTask.State.RESOLVED) {
1305
+ throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] is already resolved (did you forget to wait for an import?)`);
1123
1306
  }
1124
- }
1125
-
1126
- function _liftFlatOwn(componentTableIdx, size, memory, vals, storagePtr, storageLen) {
1127
- _debugLog('[_liftFlatOwn()] args', { size, memory, vals, storagePtr, storageLen });
1128
- throw new Error('flat lift for owned resources not yet implemented!');
1129
- }
1130
-
1131
- function _lowerFlatU8(ctx) {
1132
- _debugLog('[_lowerFlatU8()] args', ctx);
1133
- const { memory, realloc, vals, storagePtr, storageLen } = ctx;
1134
- if (vals.length !== 1) {
1135
- throw new Error('unexpected number (' + vals.length + ') of core vals (expected 1)');
1307
+ if (this.borrowedHandles.length > 0) { throw new Error('task still has borrow handles'); }
1308
+ switch (results.length) {
1309
+ case 0:
1310
+ this.onResolve(undefined);
1311
+ break;
1312
+ case 1:
1313
+ this.onResolve(results[0]);
1314
+ break;
1315
+ default:
1316
+ throw new Error('unexpected number of results');
1136
1317
  }
1137
- if (vals[0] > 255 || vals[0] < 0) { throw new Error('invalid value for core value representing u8'); }
1138
- if (!memory) { throw new Error("missing memory for lower"); }
1139
- new DataView(memory.buffer).setUint32(storagePtr, vals[0], true);
1140
- return 1;
1318
+ this.#state = AsyncTask.State.RESOLVED;
1141
1319
  }
1142
1320
 
1143
- function _lowerFlatU16(memory, vals, storagePtr, storageLen) {
1144
- _debugLog('[_lowerFlatU16()] args', { memory, vals, storagePtr, storageLen });
1145
- if (vals.length !== 1) {
1146
- throw new Error('unexpected number (' + vals.length + ') of core vals (expected 1)');
1321
+ exit() {
1322
+ _debugLog('[AsyncTask#exit()] args', { });
1323
+
1324
+ // TODO: ensure there is only one task at a time (scheduler.lock() functionality)
1325
+ if (this.#state !== AsyncTask.State.RESOLVED) {
1326
+ // TODO(fix): only fused, manually specified post returns seem to break this invariant,
1327
+ // as the TaskReturn trampoline is not activated it seems.
1328
+ //
1329
+ // see: test/p3/ported/wasmtime/component-async/post-return.js
1330
+ //
1331
+ // We *should* be able to upgrade this to be more strict and throw at some point,
1332
+ // which may involve rewriting the upstream test to surface task return manually somehow.
1333
+ //
1334
+ //throw new Error(`(component [${this.#componentIdx}]) task [${this.#id}] exited without resolution`);
1335
+ _debugLog('[AsyncTask#exit()] task exited without resolution', {
1336
+ componentIdx: this.#componentIdx,
1337
+ taskID: this.#id,
1338
+ subtask: this.getParentSubtask(),
1339
+ subtaskID: this.getParentSubtask()?.id(),
1340
+ });
1341
+ this.#state = AsyncTask.State.RESOLVED;
1147
1342
  }
1148
- if (vals[0] > 65_535 || vals[0] < 0) { throw new Error('invalid value for core value representing u16'); }
1149
- new DataView(memory.buffer).setUint16(storagePtr, vals[0], true);
1150
- return 2;
1151
- }
1152
-
1153
- function _lowerFlatU32(ctx) {
1154
- _debugLog('[_lowerFlatU32()] args', ctx);
1155
- const { memory, realloc, vals, storagePtr, storageLen } = ctx;
1156
- if (vals.length !== 1) { throw new Error('expected single value to lower, got (' + vals.length + ')'); }
1157
- if (vals[0] > 4_294_967_295 || vals[0] < 0) { throw new Error('invalid value for core value representing u32'); }
1158
1343
 
1159
- // TODO(fix): fix misaligned writes properly
1160
- const rem = ctx.storagePtr % 4;
1161
- if (rem !== 0) { ctx.storagePtr += (4 - rem); }
1344
+ if (this.borrowedHandles > 0) {
1345
+ throw new Error('task [${this.#id}] exited without clearing borrowed handles');
1346
+ }
1162
1347
 
1163
- new DataView(memory.buffer).setUint32(storagePtr, vals[0], true);
1164
- return 4;
1348
+ const state = getOrCreateAsyncState(this.#componentIdx);
1349
+ if (!state) { throw new Error('missing async state for component [' + this.#componentIdx + ']'); }
1350
+ if (!this.#isAsync && !state.inSyncExportCall) {
1351
+ throw new Error('sync task must be run from components known to be in a sync export call');
1352
+ }
1353
+ state.inSyncExportCall = false;
1354
+
1355
+ if (this.needsExclusiveLock() && !state.isExclusivelyLocked()) {
1356
+ throw new Error('task [' + this.#id + '] exit: component [' + this.#componentIdx + '] should have been exclusively locked');
1357
+ }
1358
+
1359
+ state.exclusiveRelease();
1165
1360
  }
1166
1361
 
1167
- function _lowerFlatU64(memory, vals, storagePtr, storageLen) {
1168
- _debugLog('[_lowerFlatU64()] args', { memory, vals, storagePtr, storageLen });
1169
- if (vals.length !== 1) { throw new Error('unexpected number of core vals'); }
1170
- if (vals[0] > 18_446_744_073_709_551_615n || vals[0] < 0n) { throw new Error('invalid value for core value representing u64'); }
1171
- new DataView(memory.buffer).setBigUint64(storagePtr, vals[0], true);
1172
- return 8;
1362
+ needsExclusiveLock() { return this.#needsExclusiveLock; }
1363
+
1364
+ createSubtask(args) {
1365
+ _debugLog('[AsyncTask#createSubtask()] args', args);
1366
+ const { componentIdx, childTask, callMetadata } = args;
1367
+ const newSubtask = new AsyncSubtask({
1368
+ componentIdx,
1369
+ childTask,
1370
+ parentTask: this,
1371
+ callMetadata,
1372
+ });
1373
+ this.#subtasks.push(newSubtask);
1374
+ return newSubtask;
1173
1375
  }
1174
1376
 
1175
- function _lowerFlatRecord(fieldMetas) {
1176
- return (size, memory, vals, storagePtr, storageLen) => {
1177
- const params = [...arguments].slice(5);
1178
- _debugLog('[_lowerFlatRecord()] args', {
1179
- size,
1180
- memory,
1181
- vals,
1182
- storagePtr,
1183
- storageLen,
1184
- params,
1185
- fieldMetas
1186
- });
1187
-
1188
- const [start] = vals;
1189
- if (storageLen !== undefined && size !== undefined && size > storageLen) {
1190
- throw new Error('not enough storage remaining for record flat lower');
1191
- }
1192
- const data = new Uint8Array(memory.buffer, start, size);
1193
- new Uint8Array(memory.buffer, storagePtr, size).set(data);
1194
- return data.byteLength;
1195
- }
1377
+ getLatestSubtask() { return this.#subtasks.at(-1); }
1378
+
1379
+ currentSubtask() {
1380
+ _debugLog('[AsyncTask#currentSubtask()]');
1381
+ if (this.#subtasks.length === 0) { return undefined; }
1382
+ return this.#subtasks.at(-1);
1196
1383
  }
1197
1384
 
1198
- function _lowerFlatVariant(metadata, extra) {
1199
- const { discriminantSizeBytes, lowerMetas } = metadata;
1385
+ endCurrentSubtask() {
1386
+ _debugLog('[AsyncTask#endCurrentSubtask()]');
1387
+ if (this.#subtasks.length === 0) { throw new Error('cannot end current subtask: no current subtask'); }
1388
+ const subtask = this.#subtasks.pop();
1389
+ subtask.drop();
1390
+ return subtask;
1391
+ }
1392
+ }
1393
+
1394
+ function _lowerImport(args, exportFn) {
1395
+ const params = [...arguments].slice(2);
1396
+ _debugLog('[_lowerImport()] args', { args, params, exportFn });
1397
+ const {
1398
+ functionIdx,
1399
+ componentIdx,
1400
+ isAsync,
1401
+ paramLiftFns,
1402
+ resultLowerFns,
1403
+ metadata,
1404
+ memoryIdx,
1405
+ getMemoryFn,
1406
+ getReallocFn,
1407
+ } = args;
1200
1408
 
1201
- return function _lowerFlatVariantInner(ctx) {
1202
- _debugLog('[_lowerFlatVariant()] args', ctx);
1203
- const { memory, realloc, vals, storageLen, componentIdx } = ctx;
1204
- let storagePtr = ctx.storagePtr;
1205
-
1206
- const { tag, val } = vals[0];
1207
- const variant = lowerMetas.find(vm => vm.tag === tag);
1208
- if (!variant) { throw new Error(`missing/invalid variant, no tag matches [${tag}] (options were ${variantMetas.map(vm => vm.tag)})`); }
1209
- if (!variant.discriminant) { throw new Error(`missing/invalid discriminant for variant [${variant}]`); }
1210
-
1211
- let bytesWritten;
1212
- let discriminantLowerArgs = { memory, realloc, vals: [variant.discriminant], storagePtr, componentIdx }
1213
- switch (discriminantSizeBytes) {
1214
- case 1:
1215
- bytesWritten = _lowerFlatU8(discriminantLowerArgs);
1216
- break;
1217
- case 2:
1218
- bytesWritten = _lowerFlatU16(discriminantLowerArgs);
1219
- break;
1220
- case 4:
1221
- bytesWritten = _lowerFlatU32(discriminantLowerArgs);
1222
- break;
1223
- default:
1224
- throw new Error(`unexpected discriminant size bytes [${discriminantSizeBytes}]`);
1409
+ const parentTaskMeta = getCurrentTask(componentIdx);
1410
+ const parentTask = parentTaskMeta?.task;
1411
+ if (!parentTask) { throw new Error('missing parent task during lower of import'); }
1412
+
1413
+ const cstate = getOrCreateAsyncState(componentIdx);
1414
+
1415
+ const subtask = parentTask.createSubtask({
1416
+ componentIdx,
1417
+ parentTask,
1418
+ callMetadata: {
1419
+ memoryIdx,
1420
+ memory: getMemoryFn(),
1421
+ realloc: getReallocFn(),
1422
+ resultPtr: params[0],
1225
1423
  }
1226
- if (bytesWritten !== discriminantSizeBytes) {
1227
- throw new Error("unexpectedly wrote more bytes than discriminant");
1424
+ });
1425
+ parentTask.setReturnMemoryIdx(memoryIdx);
1426
+
1427
+ const rep = cstate.subtasks.insert(subtask);
1428
+ subtask.setRep(rep);
1429
+
1430
+ subtask.setOnProgressFn(() => {
1431
+ subtask.setPendingEventFn(() => {
1432
+ if (subtask.resolved()) { subtask.deliverResolve(); }
1433
+ return {
1434
+ code: ASYNC_EVENT_CODE.SUBTASK,
1435
+ index: rep,
1436
+ result: subtask.getStateNumber(),
1437
+ }
1438
+ });
1439
+ });
1440
+
1441
+ // Set up a handler on subtask completion to lower results from the call into the caller's memory region.
1442
+ subtask.registerOnResolveHandler((res) => {
1443
+ _debugLog('[_lowerImport()] handling subtask result', { res, subtaskID: subtask.id() });
1444
+ const { memory, resultPtr, realloc } = subtask.getCallMetadata();
1445
+ if (resultLowerFns.length === 0) { return; }
1446
+ resultLowerFns[0]({ componentIdx, memory, realloc, vals: [res], storagePtr: resultPtr });
1447
+ });
1448
+
1449
+ const subtaskState = subtask.getStateNumber();
1450
+ if (subtaskState < 0 || subtaskState > 2**5) {
1451
+ throw new Error('invalid subtask state, out of valid range');
1452
+ }
1453
+
1454
+ // NOTE: we must wait a bit before calling the export function,
1455
+ // to ensure the subtask state is not modified before the lower call return
1456
+ //
1457
+ // TODO: we should trigger via subtask state changing, rather than a static wait?
1458
+ setTimeout(async () => {
1459
+ try {
1460
+ _debugLog('[_lowerImport()] calling lowered import', { exportFn, params });
1461
+ exportFn.apply(null, params);
1462
+
1463
+ const task = subtask.getChildTask();
1464
+ task.registerOnResolveHandler((res) => {
1465
+ _debugLog('[_lowerImport()] cascading subtask completion', {
1466
+ childTaskID: task.id(),
1467
+ subtaskID: subtask.id(),
1468
+ parentTaskID: parentTask.id(),
1469
+ });
1470
+
1471
+ subtask.onResolve(res);
1472
+
1473
+ cstate.tick();
1474
+ });
1475
+ } catch (err) {
1476
+ console.error("post-lower import fn error:", err);
1477
+ throw err;
1228
1478
  }
1229
- storagePtr += bytesWritten;
1230
-
1231
- bytesWritten += variant.lowerFn({ memory, realloc, vals: [val], storagePtr, storageLen, componentIdx });
1232
-
1233
- return bytesWritten;
1234
- }
1479
+ }, 100);
1480
+
1481
+ return Number(subtask.waitableRep()) << 4 | subtaskState;
1235
1482
  }
1236
1483
 
1237
- function _lowerFlatList(size, memory, vals, storagePtr, storageLen) {
1238
- _debugLog('[_lowerFlatList()] args', { size, memory, vals, storagePtr, storageLen });
1239
- let [start, len] = vals;
1240
- const totalSizeBytes = len * size;
1241
- if (storageLen !== undefined && totalSizeBytes > storageLen) {
1242
- throw new Error('not enough storage remaining for list flat lower');
1484
+ function _liftFlatU8(ctx) {
1485
+ _debugLog('[_liftFlatU8()] args', { ctx });
1486
+ let val;
1487
+
1488
+ if (ctx.useDirectParams) {
1489
+ if (ctx.params.length === 0) { throw new Error('expected at least a single i32 argument'); }
1490
+ val = ctx.params[0];
1491
+ ctx.params = ctx.params.slice(1);
1492
+ return [val, ctx];
1243
1493
  }
1244
- const data = new Uint8Array(memory.buffer, start, totalSizeBytes);
1245
- new Uint8Array(memory.buffer, storagePtr, totalSizeBytes).set(data);
1246
- return data.byteLength;
1247
- }
1248
-
1249
- function _lowerFlatEnum(size, memory, vals, storagePtr, storageLen) {
1250
- _debugLog('[_lowerFlatEnum()] args', { size, memory, vals, storagePtr, storageLen });
1251
- let [start] = vals;
1252
- if (storageLen !== undefined && size !== undefined && size > storageLen) {
1253
- throw new Error('not enough storage remaining for enum flat lower');
1494
+
1495
+ if (ctx.storageLen !== undefined && ctx.storageLen < ctx.storagePtr + 1) {
1496
+ throw new Error('not enough storage remaining for lift');
1254
1497
  }
1255
- const data = new Uint8Array(memory.buffer, start, size);
1256
- new Uint8Array(memory.buffer, storagePtr, size).set(data);
1257
- return data.byteLength;
1498
+ val = new DataView(ctx.memory.buffer).getUint8(ctx.storagePtr, true);
1499
+ ctx.storagePtr += 1;
1500
+ if (ctx.storageLen !== undefined) { ctx.storageLen -= 1; }
1501
+
1502
+ return [val, ctx];
1258
1503
  }
1259
1504
 
1260
- function _lowerFlatOption(size, memory, vals, storagePtr, storageLen) {
1261
- _debugLog('[_lowerFlatOption()] args', { size, memory, vals, storagePtr, storageLen });
1262
- let [start] = vals;
1263
- if (storageLen !== undefined && size !== undefined && size > storageLen) {
1264
- throw new Error('not enough storage remaining for option flat lower');
1505
+ function _liftFlatU16(ctx) {
1506
+ _debugLog('[_liftFlatU16()] args', { ctx });
1507
+ let val;
1508
+
1509
+ if (ctx.useDirectParams) {
1510
+ if (params.length === 0) { throw new Error('expected at least a single i32 argument'); }
1511
+ val = ctx.params[0];
1512
+ ctx.params = ctx.params.slice(1);
1513
+ return [val, ctx];
1265
1514
  }
1266
- const data = new Uint8Array(memory.buffer, start, size);
1267
- new Uint8Array(memory.buffer, storagePtr, size).set(data);
1268
- return data.byteLength;
1269
- }
1270
-
1271
- function _lowerFlatResult(lowerMetas) {
1272
- const invalidTag = lowerMetas.find(t => t.tag !== 'ok' && t.tag !== 'error')
1273
- if (invalidTag) { throw new Error(`invalid variant tag [${invalidTag}] found for result`); }
1274
1515
 
1275
- return function _lowerFlatResultInner() {
1276
- _debugLog('[_lowerFlatResult()] args', { lowerMetas });
1277
- let lowerFn = _lowerFlatVariant({ discriminantSizeBytes: 1, lowerMetas }, { forResult: true });
1278
- return lowerFn.apply(null, arguments);
1279
- };
1280
- }
1281
-
1282
- function _lowerFlatOwn(size, memory, vals, storagePtr, storageLen) {
1283
- _debugLog('[_lowerFlatOwn()] args', { size, memory, vals, storagePtr, storageLen });
1284
- throw new Error('flat lower for owned resources not yet implemented!');
1285
- }
1286
- const ASYNC_STATE = new Map();
1287
-
1288
- function getOrCreateAsyncState(componentIdx, init) {
1289
- if (!ASYNC_STATE.has(componentIdx)) {
1290
- const newState = new ComponentAsyncState({ componentIdx });
1291
- ASYNC_STATE.set(componentIdx, newState);
1516
+ if (ctx.storageLen !== undefined && ctx.storageLen < ctx.storagePtr + 2) {
1517
+ throw new Error('not enough storage remaining for lift');
1292
1518
  }
1293
- return ASYNC_STATE.get(componentIdx);
1519
+ val = new DataView(ctx.memory.buffer).getUint16(ctx.storagePtr, true);
1520
+ ctx.storagePtr += 2;
1521
+ if (ctx.storageLen !== undefined) { ctx.storageLen -= 2; }
1522
+
1523
+ return [val, ctx];
1294
1524
  }
1295
1525
 
1296
- class ComponentAsyncState {
1297
- static EVENT_HANDLER_EVENTS = [ 'backpressure-change' ];
1298
-
1299
- #componentIdx;
1300
- #callingAsyncImport = false;
1301
- #syncImportWait = promiseWithResolvers();
1302
- #locked = false;
1303
- #parkedTasks = new Map();
1304
- #suspendedTasksByTaskID = new Map();
1305
- #suspendedTaskIDs = [];
1306
- #pendingTasks = [];
1307
- #errored = null;
1308
-
1309
- #backpressure = 0;
1310
- #backpressureWaiters = 0n;
1311
-
1312
- #handlerMap = new Map();
1313
- #nextHandlerID = 0n;
1314
-
1315
- mayLeave = true;
1316
-
1317
- waitableSets;
1318
- waitables;
1319
- subtasks;
1320
-
1321
- constructor(args) {
1322
- this.#componentIdx = args.componentIdx;
1323
- this.waitableSets = new RepTable({ target: `component [${this.#componentIdx}] waitable sets` });
1324
- this.waitables = new RepTable({ target: `component [${this.#componentIdx}] waitables` });
1325
- this.subtasks = new RepTable({ target: `component [${this.#componentIdx}] subtasks` });
1326
- };
1327
-
1328
- componentIdx() { return this.#componentIdx; }
1526
+ function _liftFlatU32(ctx) {
1527
+ _debugLog('[_liftFlatU32()] args', { ctx });
1528
+ let val;
1329
1529
 
1330
- errored() { return this.#errored !== null; }
1331
- setErrored(err) {
1332
- _debugLog('[ComponentAsyncState#setErrored()] component errored', { err, componentIdx: this.#componentIdx });
1333
- if (this.#errored) { return; }
1334
- if (!err) {
1335
- err = new Error('error elswehere (see other component instance error)')
1336
- err.componentIdx = this.#componentIdx;
1337
- }
1338
- this.#errored = err;
1530
+ if (ctx.useDirectParams) {
1531
+ if (ctx.params.length === 0) { throw new Error('expected at least a single i34 argument'); }
1532
+ val = ctx.params[0];
1533
+ ctx.params = ctx.params.slice(1);
1534
+ return [val, ctx];
1339
1535
  }
1340
1536
 
1341
- callingSyncImport(val) {
1342
- if (val === undefined) { return this.#callingAsyncImport; }
1343
- if (typeof val !== 'boolean') { throw new TypeError('invalid setting for async import'); }
1344
- const prev = this.#callingAsyncImport;
1345
- this.#callingAsyncImport = val;
1346
- if (prev === true && this.#callingAsyncImport === false) {
1347
- this.#notifySyncImportEnd();
1348
- }
1537
+ if (ctx.storageLen !== undefined && ctx.storageLen < ctx.storagePtr + 4) {
1538
+ throw new Error('not enough storage remaining for lift');
1349
1539
  }
1540
+ val = new DataView(ctx.memory.buffer).getUint32(ctx.storagePtr, true);
1541
+ ctx.storagePtr += 4;
1542
+ if (ctx.storageLen !== undefined) { ctx.storageLen -= 4; }
1350
1543
 
1351
- #notifySyncImportEnd() {
1352
- const existing = this.#syncImportWait;
1353
- this.#syncImportWait = promiseWithResolvers();
1354
- existing.resolve();
1355
- }
1544
+ return [val, ctx];
1545
+ }
1546
+
1547
+ function _liftFlatU64(ctx) {
1548
+ _debugLog('[_liftFlatU64()] args', { ctx });
1549
+ let val;
1356
1550
 
1357
- async waitForSyncImportCallEnd() {
1358
- await this.#syncImportWait.promise;
1551
+ if (ctx.useDirectParams) {
1552
+ if (ctx.params.length === 0) { throw new Error('expected at least one single i64 argument'); }
1553
+ if (typeof ctx.params[0] !== 'bigint') { throw new Error('expected bigint'); }
1554
+ val = ctx.params[0];
1555
+ ctx.params = ctx.params.slice(1);
1556
+ return [val, ctx];
1359
1557
  }
1360
1558
 
1361
- setBackpressure(v) { this.#backpressure = v; }
1362
- getBackpressure(v) { return this.#backpressure; }
1363
- incrementBackpressure() {
1364
- const newValue = this.getBackpressure() + 1;
1365
- if (newValue > 2**16) { throw new Error("invalid backpressure value, overflow"); }
1366
- this.setBackpressure(newValue);
1367
- }
1368
- decrementBackpressure() {
1369
- this.setBackpressure(Math.max(0, this.getBackpressure() - 1));
1559
+ if (ctx.storageLen !== undefined && ctx.storageLen < ctx.storagePtr + 8) {
1560
+ throw new Error('not enough storage remaining for lift');
1370
1561
  }
1371
- hasBackpressure() { return this.#backpressure > 0; }
1562
+ val = new DataView(ctx.memory.buffer).getUint64(ctx.storagePtr, true);
1563
+ ctx.storagePtr += 8;
1564
+ if (ctx.storageLen !== undefined) { ctx.storageLen -= 8; }
1372
1565
 
1373
- waitForBackpressure() {
1374
- let backpressureCleared = false;
1375
- const cstate = this;
1376
- cstate.addBackpressureWaiter();
1377
- const handlerID = this.registerHandler({
1378
- event: 'backpressure-change',
1379
- fn: (bp) => {
1380
- if (bp === 0) {
1381
- cstate.removeHandler(handlerID);
1382
- backpressureCleared = true;
1383
- }
1384
- }
1385
- });
1386
- return new Promise((resolve) => {
1387
- const interval = setInterval(() => {
1388
- if (backpressureCleared) { return; }
1389
- clearInterval(interval);
1390
- cstate.removeBackpressureWaiter();
1391
- resolve(null);
1392
- }, 0);
1393
- });
1394
- }
1566
+ return [val, ctx];
1567
+ }
1568
+
1569
+ function _liftFlatStringUTF8(ctx) {
1570
+ _debugLog('[_liftFlatStringUTF8()] args', { ctx });
1571
+ let val;
1395
1572
 
1396
- registerHandler(args) {
1397
- const { event, fn } = args;
1398
- if (!event) { throw new Error("missing handler event"); }
1399
- if (!fn) { throw new Error("missing handler fn"); }
1400
-
1401
- if (!ComponentAsyncState.EVENT_HANDLER_EVENTS.includes(event)) {
1402
- throw new Error(`unrecognized event handler [${event}]`);
1403
- }
1404
-
1405
- const handlerID = this.#nextHandlerID++;
1406
- let handlers = this.#handlerMap.get(event);
1407
- if (!handlers) {
1408
- handlers = [];
1409
- this.#handlerMap.set(event, handlers)
1410
- }
1411
-
1412
- handlers.push({ id: handlerID, fn, event });
1413
- return handlerID;
1573
+ if (ctx.useDirectParams) {
1574
+ if (ctx.params.length < 2) { throw new Error('expected at least two u32 arguments'); }
1575
+ const offset = ctx.params[0];
1576
+ if (!Number.isSafeInteger(offset)) { throw new Error('invalid offset'); }
1577
+ const len = ctx.params[1];
1578
+ if (!Number.isSafeInteger(len)) { throw new Error('invalid len'); }
1579
+ val = TEXT_DECODER_UTF8.decode(new DataView(ctx.memory.buffer, offset, len));
1580
+ ctx.params = ctx.params.slice(2);
1581
+ return [val, ctx];
1414
1582
  }
1415
1583
 
1416
- removeHandler(args) {
1417
- const { event, handlerID } = args;
1418
- const registeredHandlers = this.#handlerMap.get(event);
1419
- if (!registeredHandlers) { return; }
1420
- const found = registeredHandlers.find(h => h.id === handlerID);
1421
- if (!found) { return; }
1422
- this.#handlerMap.set(event, this.#handlerMap.get(event).filter(h => h.id !== handlerID));
1423
- }
1584
+ const start = new DataView(ctx.memory.buffer).getUint32(ctx.storagePtr, params[0], true);
1585
+ const codeUnits = new DataView(memory.buffer).getUint32(ctx.storagePtr, params[0] + 4, true);
1586
+ val = TEXT_DECODER_UTF8.decode(new Uint8Array(ctx.memory.buffer, start, codeUnits));
1587
+ ctx.storagePtr += codeUnits;
1588
+ if (ctx.storageLen !== undefined) { ctx.storageLen -= codeUnits; }
1424
1589
 
1425
- getBackpressureWaiters() { return this.#backpressureWaiters; }
1426
- addBackpressureWaiter() { this.#backpressureWaiters++; }
1427
- removeBackpressureWaiter() {
1428
- this.#backpressureWaiters--;
1429
- if (this.#backpressureWaiters < 0) {
1430
- throw new Error("unexepctedly negative number of backpressure waiters");
1590
+ return [val, ctx];
1591
+ }
1592
+
1593
+ function _liftFlatVariant(casesAndLiftFns) {
1594
+ return function _liftFlatVariantInner(ctx) {
1595
+ _debugLog('[_liftFlatVariant()] args', { ctx });
1596
+
1597
+ const origUseParams = ctx.useDirectParams;
1598
+
1599
+ let caseIdx;
1600
+ if (casesAndLiftFns.length < 256) {
1601
+ let discriminantByteLen = 1;
1602
+ const [idx, newCtx] = _liftFlatU8(ctx);
1603
+ caseIdx = idx;
1604
+ ctx = newCtx;
1605
+ } else if (casesAndLiftFns.length > 256 && discriminantByteLen < 65536) {
1606
+ discriminantByteLen = 2;
1607
+ const [idx, newCtx] = _liftFlatU16(ctx);
1608
+ caseIdx = idx;
1609
+ ctx = newCtx;
1610
+ } else if (casesAndLiftFns.length > 65536 && discriminantByteLen < 4_294_967_296) {
1611
+ discriminantByteLen = 4;
1612
+ const [idx, newCtx] = _liftFlatU32(ctx);
1613
+ caseIdx = idx;
1614
+ ctx = newCtx;
1615
+ } else {
1616
+ throw new Error('unsupported number of cases [' + casesAndLIftFns.legnth + ']');
1431
1617
  }
1432
- }
1433
-
1434
- parkTaskOnAwaitable(args) {
1435
- if (!args.awaitable) { throw new TypeError('missing awaitable when trying to park'); }
1436
- if (!args.task) { throw new TypeError('missing task when trying to park'); }
1437
- const { awaitable, task } = args;
1438
1618
 
1439
- let taskList = this.#parkedTasks.get(awaitable.id());
1440
- if (!taskList) {
1441
- taskList = [];
1442
- this.#parkedTasks.set(awaitable.id(), taskList);
1619
+ const [ tag, liftFn, size32, alignment32 ] = casesAndLiftFns[caseIdx];
1620
+
1621
+ let val;
1622
+ if (liftFn === null) {
1623
+ val = { tag };
1624
+ return [val, ctx];
1443
1625
  }
1444
- taskList.push(task);
1445
1626
 
1446
- this.wakeNextTaskForAwaitable(awaitable);
1627
+ const [newVal, newCtx] = liftFn(ctx);
1628
+ ctx = newCtx;
1629
+ val = { tag, val: newVal };
1630
+
1631
+ return [val, ctx];
1447
1632
  }
1448
-
1449
- wakeNextTaskForAwaitable(awaitable) {
1450
- if (!awaitable) { throw new TypeError('missing awaitable when waking next task'); }
1451
- const awaitableID = awaitable.id();
1633
+ }
1634
+
1635
+ function _liftFlatList(elemLiftFn, alignment32, knownLen) {
1636
+ function _liftFlatListInner(ctx) {
1637
+ _debugLog('[_liftFlatList()] args', { ctx });
1452
1638
 
1453
- const taskList = this.#parkedTasks.get(awaitableID);
1454
- if (!taskList || taskList.length === 0) {
1455
- _debugLog('[ComponentAsyncState] no tasks waiting for awaitable', { awaitableID: awaitable.id() });
1456
- return;
1639
+ let metaPtr;
1640
+ let dataPtr;
1641
+ let len;
1642
+ if (ctx.useDirectParams) {
1643
+ if (knownLen) {
1644
+ dataPtr = _liftFlatU32(ctx);
1645
+ } else {
1646
+ metaPtr = _liftFlatU32(ctx);
1647
+ }
1648
+ } else {
1649
+ if (knownLen) {
1650
+ dataPtr = _liftFlatU32(ctx);
1651
+ } else {
1652
+ metaPtr = _liftFlatU32(ctx);
1653
+ }
1457
1654
  }
1458
1655
 
1459
- let task = taskList.shift(); // todo(perf)
1460
- if (!task) { throw new Error('no task in parked list despite previous check'); }
1656
+ if (metaPtr) {
1657
+ if (dataPtr !== undefined) { throw new Error('both meta and data pointers should not be set yet'); }
1658
+
1659
+ if (ctx.useDirectParams) {
1660
+ ctx.useDirectParams = false;
1661
+ ctx.storagePtr = metaPtr;
1662
+ ctx.storageLen = 8;
1663
+
1664
+ dataPtr = _liftFlatU32(ctx);
1665
+ len = _liftFlatU32(ctx);
1666
+
1667
+ ctx.useDirectParams = true;
1668
+ ctx.storagePtr = null;
1669
+ ctx.storageLen = null;
1670
+ } else {
1671
+ dataPtr = _liftFlatU32(ctx);
1672
+ len = _liftFlatU32(ctx);
1673
+ }
1674
+ }
1461
1675
 
1462
- if (!task.awaitableResume) {
1463
- throw new Error('task ready due to awaitable is missing resume', { taskID: task.id(), awaitableID });
1676
+ const val = [];
1677
+ for (var i = 0; i < len; i++) {
1678
+ ctx.storagePtr = Math.ceil(ctx.storagePtr / alignment32) * alignment32;
1679
+ const [res, nextCtx] = elemLiftFn(ctx);
1680
+ val.push(res);
1681
+ ctx = nextCtx;
1464
1682
  }
1465
- task.awaitableResume();
1683
+
1684
+ return [val, ctx];
1466
1685
  }
1467
-
1468
- // TODO: we might want to check for pre-locked status here
1469
- exclusiveLock() {
1470
- this.#locked = true;
1686
+ }
1687
+
1688
+ function _liftFlatFlags(cases) {
1689
+ return function _liftFlatFlagsInner(ctx) {
1690
+ _debugLog('[_liftFlatFlags()] args', { ctx });
1691
+ throw new Error('flat lift for flags not yet implemented!');
1471
1692
  }
1472
-
1473
- exclusiveRelease() {
1474
- _debugLog('[ComponentAsyncState#exclusiveRelease()] releasing', {
1475
- locked: this.#locked,
1476
- componentIdx: this.#componentIdx,
1477
- });
1478
-
1479
- this.#locked = false
1693
+ }
1694
+
1695
+ function _liftFlatResult(casesAndLiftFns) {
1696
+ return function _liftFlatResultInner(ctx) {
1697
+ _debugLog('[_liftFlatResult()] args', { ctx });
1698
+ return _liftFlatVariant(casesAndLiftFns)(ctx);
1480
1699
  }
1481
-
1482
- isExclusivelyLocked() { return this.#locked === true; }
1483
-
1484
- #getSuspendedTaskMeta(taskID) {
1485
- return this.#suspendedTasksByTaskID.get(taskID);
1700
+ }
1701
+
1702
+ function _liftFlatBorrow(componentTableIdx, size, memory, vals, storagePtr, storageLen) {
1703
+ _debugLog('[_liftFlatBorrow()] args', { size, memory, vals, storagePtr, storageLen });
1704
+ throw new Error('flat lift for borrowed resources not yet implemented!');
1705
+ }
1706
+
1707
+ function _lowerFlatU8(ctx) {
1708
+ _debugLog('[_lowerFlatU8()] args', ctx);
1709
+ const { memory, realloc, vals, storagePtr, storageLen } = ctx;
1710
+ if (vals.length !== 1) {
1711
+ throw new Error('unexpected number (' + vals.length + ') of core vals (expected 1)');
1486
1712
  }
1487
-
1488
- #removeSuspendedTaskMeta(taskID) {
1489
- _debugLog('[ComponentAsyncState#removeSuspendedTaskMeta()] removing suspended task', { taskID });
1490
- const idx = this.#suspendedTaskIDs.findIndex(t => t === taskID);
1491
- const meta = this.#suspendedTasksByTaskID.get(taskID);
1492
- this.#suspendedTaskIDs[idx] = null;
1493
- this.#suspendedTasksByTaskID.delete(taskID);
1494
- return meta;
1713
+ if (vals[0] > 255 || vals[0] < 0) { throw new Error('invalid value for core value representing u8'); }
1714
+ if (!memory) { throw new Error("missing memory for lower"); }
1715
+ new DataView(memory.buffer).setUint32(storagePtr, vals[0], true);
1716
+ return 1;
1717
+ }
1718
+
1719
+ function _lowerFlatU16(memory, vals, storagePtr, storageLen) {
1720
+ _debugLog('[_lowerFlatU16()] args', { memory, vals, storagePtr, storageLen });
1721
+ if (vals.length !== 1) {
1722
+ throw new Error('unexpected number (' + vals.length + ') of core vals (expected 1)');
1495
1723
  }
1724
+ if (vals[0] > 65_535 || vals[0] < 0) { throw new Error('invalid value for core value representing u16'); }
1725
+ new DataView(memory.buffer).setUint16(storagePtr, vals[0], true);
1726
+ return 2;
1727
+ }
1728
+
1729
+ function _lowerFlatU32(ctx) {
1730
+ _debugLog('[_lowerFlatU32()] args', { ctx });
1731
+ const { memory, realloc, vals, storagePtr, storageLen } = ctx;
1732
+ if (vals.length !== 1) { throw new Error('expected single value to lower, got (' + vals.length + ')'); }
1733
+ if (vals[0] > 4_294_967_295 || vals[0] < 0) { throw new Error('invalid value for core value representing u32'); }
1496
1734
 
1497
- #addSuspendedTaskMeta(meta) {
1498
- if (!meta) { throw new Error('missing task meta'); }
1499
- const taskID = meta.taskID;
1500
- this.#suspendedTasksByTaskID.set(taskID, meta);
1501
- this.#suspendedTaskIDs.push(taskID);
1502
- if (this.#suspendedTasksByTaskID.size < this.#suspendedTaskIDs.length - 10) {
1503
- this.#suspendedTaskIDs = this.#suspendedTaskIDs.filter(t => t !== null);
1735
+ // TODO(refactor): fail loudly on misaligned flat lowers?
1736
+ const rem = ctx.storagePtr % 4;
1737
+ if (rem !== 0) { ctx.storagePtr += (4 - rem); }
1738
+
1739
+ new DataView(memory.buffer).setUint32(storagePtr, vals[0], true);
1740
+
1741
+ return 4;
1742
+ }
1743
+
1744
+ function _lowerFlatU64(memory, vals, storagePtr, storageLen) {
1745
+ _debugLog('[_lowerFlatU64()] args', { memory, vals, storagePtr, storageLen });
1746
+ if (vals.length !== 1) { throw new Error('unexpected number of core vals'); }
1747
+ if (vals[0] > 18_446_744_073_709_551_615n || vals[0] < 0n) { throw new Error('invalid value for core value representing u64'); }
1748
+ new DataView(memory.buffer).setBigUint64(storagePtr, vals[0], true);
1749
+ return 8;
1750
+ }
1751
+
1752
+ function _lowerFlatRecord(fieldMetas) {
1753
+ return (size, memory, vals, storagePtr, storageLen) => {
1754
+ const params = [...arguments].slice(5);
1755
+ _debugLog('[_lowerFlatRecord()] args', {
1756
+ size,
1757
+ memory,
1758
+ vals,
1759
+ storagePtr,
1760
+ storageLen,
1761
+ params,
1762
+ fieldMetas
1763
+ });
1764
+
1765
+ const [start] = vals;
1766
+ if (storageLen !== undefined && size !== undefined && size > storageLen) {
1767
+ throw new Error('not enough storage remaining for record flat lower');
1504
1768
  }
1769
+ const data = new Uint8Array(memory.buffer, start, size);
1770
+ new Uint8Array(memory.buffer, storagePtr, size).set(data);
1771
+ return data.byteLength;
1505
1772
  }
1773
+ }
1774
+
1775
+ function _lowerFlatVariant(metadata, extra) {
1776
+ const { discriminantSizeBytes, lowerMetas } = metadata;
1506
1777
 
1507
- suspendTask(args) {
1508
- // TODO(threads): readyFn is normally on the thread
1509
- const { task, readyFn } = args;
1510
- const taskID = task.id();
1511
- _debugLog('[ComponentAsyncState#suspendTask()]', { taskID });
1778
+ return function _lowerFlatVariantInner(ctx) {
1779
+ _debugLog('[_lowerFlatVariant()] args', ctx);
1780
+ const { memory, realloc, vals, storageLen, componentIdx } = ctx;
1781
+ let storagePtr = ctx.storagePtr;
1512
1782
 
1513
- if (this.#getSuspendedTaskMeta(taskID)) {
1514
- throw new Error('task [' + taskID + '] already suspended');
1783
+ const { tag, val } = vals[0];
1784
+ const variant = lowerMetas.find(vm => vm.tag === tag);
1785
+ if (!variant) { throw new Error(`missing/invalid variant, no tag matches [${tag}] (options were ${variantMetas.map(vm => vm.tag)})`); }
1786
+ if (!variant.discriminant) { throw new Error(`missing/invalid discriminant for variant [${variant}]`); }
1787
+
1788
+ let bytesWritten;
1789
+ let discriminantLowerArgs = { memory, realloc, vals: [variant.discriminant], storagePtr, componentIdx }
1790
+ switch (discriminantSizeBytes) {
1791
+ case 1:
1792
+ bytesWritten = _lowerFlatU8(discriminantLowerArgs);
1793
+ break;
1794
+ case 2:
1795
+ bytesWritten = _lowerFlatU16(discriminantLowerArgs);
1796
+ break;
1797
+ case 4:
1798
+ bytesWritten = _lowerFlatU32(discriminantLowerArgs);
1799
+ break;
1800
+ default:
1801
+ throw new Error(`unexpected discriminant size bytes [${discriminantSizeBytes}]`);
1802
+ }
1803
+ if (bytesWritten !== discriminantSizeBytes) {
1804
+ throw new Error("unexpectedly wrote more bytes than discriminant");
1515
1805
  }
1806
+ storagePtr += bytesWritten;
1516
1807
 
1517
- const { promise, resolve } = Promise.withResolvers();
1518
- this.#addSuspendedTaskMeta({
1519
- task,
1520
- taskID,
1521
- readyFn,
1522
- resume: () => {
1523
- _debugLog('[ComponentAsyncState#suspendTask()] resuming suspended task', { taskID });
1524
- // TODO(threads): it's thread cancellation we should be checking for below, not task
1525
- resolve(!task.isCancelled());
1526
- },
1527
- });
1808
+ bytesWritten += variant.lowerFn({ memory, realloc, vals: [val], storagePtr, storageLen, componentIdx });
1528
1809
 
1529
- return promise;
1530
- }
1531
-
1532
- resumeTaskByID(taskID) {
1533
- const meta = this.#removeSuspendedTaskMeta(taskID);
1534
- if (!meta) { return; }
1535
- if (meta.taskID !== taskID) { throw new Error('task ID does not match'); }
1536
- meta.resume();
1810
+ return bytesWritten;
1537
1811
  }
1812
+ }
1813
+
1814
+ function _lowerFlatList(args) {
1815
+ const { elemLowerFn } = args;
1816
+ if (!elemLowerFn) { throw new TypeError("missing/invalid element lower fn for list"); }
1538
1817
 
1539
- tick() {
1540
- _debugLog('[ComponentAsyncState#tick()]', { suspendedTaskIDs: this.#suspendedTaskIDs });
1541
- let resumedTask = false;
1542
- for (const taskID of this.#suspendedTaskIDs.filter(t => t !== null)) {
1543
- const meta = this.#suspendedTasksByTaskID.get(taskID);
1544
- if (!meta || !meta.readyFn) {
1545
- throw new Error('missing/invalid task despite ID [' + taskID + '] being present');
1818
+ return function _lowerFlatListInner(ctx) {
1819
+ _debugLog('[_lowerFlatList()] args', { ctx });
1820
+
1821
+ if (ctx.params.length < 2) { throw new Error('insufficient params left to lower list'); }
1822
+ const storagePtr = ctx.params[0];
1823
+ const elemCount = ctx.params[1];
1824
+ ctx.params = ctx.params.slice(2);
1825
+
1826
+ if (ctx.useDirectParams) {
1827
+ const list = ctx.vals[0];
1828
+ if (!list) { throw new Error("missing direct param value"); }
1829
+
1830
+ const elemLowerCtx = { storagePtr, memory: ctx.memory };
1831
+ for (let idx = 0; idx < list.length; idx++) {
1832
+ elemLowerCtx.vals = list.slice(idx, idx+1);
1833
+ elemLowerCtx.storagePtr += elemLowerFn(elemLowerCtx);
1546
1834
  }
1547
- if (!meta.readyFn()) { continue; }
1548
- resumedTask = true;
1549
- this.resumeTaskByID(taskID);
1835
+
1836
+ const bytesLowered = elemLowerCtx.storagePtr - ctx.storagePtr;
1837
+ ctx.storagePtr = elemLowerCtx.storagePtr;
1838
+ return bytesLowered;
1550
1839
  }
1551
- return resumedTask;
1552
- }
1553
-
1554
- addPendingTask(task) {
1555
- this.#pendingTasks.push(task);
1840
+
1841
+
1842
+ if (ctx.vals.length !== 2) {
1843
+ throw new Error('indirect parameter loading must have a pointer and length as vals');
1844
+ }
1845
+ let [valStartPtr, valLen] = ctx.vals;
1846
+ const totalSizeBytes = valLen * size;
1847
+ if (ctx.storageLen !== undefined && totalSizeBytes > ctx.storageLen) {
1848
+ throw new Error('not enough storage remaining for list flat lower');
1849
+ }
1850
+
1851
+ const data = new Uint8Array(memory.buffer, valStartPtr, totalSizeBytes);
1852
+ new Uint8Array(memory.buffer, storagePtr, totalSizeBytes).set(data);
1853
+
1854
+ return totalSizeBytes;
1556
1855
  }
1557
1856
  }
1558
1857
 
1559
- function _prepareCall(
1560
- memoryIdx,
1561
- getMemoryFn,
1562
- startFn,
1563
- returnFn,
1564
- callerInstanceIdx,
1565
- calleeInstanceIdx,
1566
- taskReturnTypeIdx,
1567
- isCalleeAsyncInt,
1568
- stringEncoding,
1569
- resultCountOrAsync,
1570
- ) {
1571
- _debugLog('[_prepareCall()]', {
1572
- callerInstanceIdx,
1573
- calleeInstanceIdx,
1574
- taskReturnTypeIdx,
1575
- isCalleeAsyncInt,
1576
- stringEncoding,
1577
- resultCountOrAsync,
1578
- });
1579
- const argArray = [...arguments];
1580
-
1581
- // Since Rust will happily pass large u32s over, resultCountOrAsync should be one of:
1582
- // (a) u32 max size => callee is async fn with no result
1583
- // (b) u32 max size - 1 => callee is async fn with result
1584
- // (c) any other value => callee is sync with the given result count
1585
- //
1586
- // Due to JS handling the value as 2s complement, the `resultCountOrAsync` ends up being:
1587
- // (a) -1 as u32 max size
1588
- // (b) -2 as u32 max size - 1
1589
- // (c) x
1590
- //
1591
- // Due to JS mishandling the value as 2s complement, the actual values we get are:
1592
- // see. https://github.com/wasm-bindgen/wasm-bindgen/issues/1388
1593
- let isAsync = false;
1594
- let hasResultPointer = false;
1595
- if (resultCountOrAsync === -1) {
1596
- isAsync = true;
1597
- hasResultPointer = false;
1598
- } else if (resultCountOrAsync === -2) {
1599
- isAsync = true;
1600
- hasResultPointer = true;
1858
+ function _lowerFlatTuple(size, memory, vals, storagePtr, storageLen) {
1859
+ _debugLog('[_lowerFlatTuple()] args', { size, memory, vals, storagePtr, storageLen });
1860
+ let [start, len] = vals;
1861
+ if (storageLen !== undefined && len > storageLen) {
1862
+ throw new Error('not enough storage remaining for tuple flat lower');
1601
1863
  }
1602
-
1603
- const currentCallerTaskMeta = getCurrentTask(callerInstanceIdx);
1604
- if (!currentCallerTaskMeta) {
1605
- throw new Error('invalid/missing current task for caller during prepare call');
1864
+ const data = new Uint8Array(memory.buffer, start, len);
1865
+ new Uint8Array(memory.buffer, storagePtr, len).set(data);
1866
+ return data.byteLength;
1867
+ }
1868
+
1869
+ function _lowerFlatEnum(size, memory, vals, storagePtr, storageLen) {
1870
+ _debugLog('[_lowerFlatEnum()] args', { size, memory, vals, storagePtr, storageLen });
1871
+ let [start] = vals;
1872
+ if (storageLen !== undefined && size !== undefined && size > storageLen) {
1873
+ throw new Error('not enough storage remaining for enum flat lower');
1606
1874
  }
1607
-
1608
- const currentCallerTask = currentCallerTaskMeta.task;
1609
- if (!currentCallerTask) {
1610
- throw new Error('unexpectedly missing task in meta for caller during prepare call');
1875
+ const data = new Uint8Array(memory.buffer, start, size);
1876
+ new Uint8Array(memory.buffer, storagePtr, size).set(data);
1877
+ return data.byteLength;
1878
+ }
1879
+
1880
+ function _lowerFlatOption(size, memory, vals, storagePtr, storageLen) {
1881
+ _debugLog('[_lowerFlatOption()] args', { size, memory, vals, storagePtr, storageLen });
1882
+ let [start] = vals;
1883
+ if (storageLen !== undefined && size !== undefined && size > storageLen) {
1884
+ throw new Error('not enough storage remaining for option flat lower');
1611
1885
  }
1886
+ const data = new Uint8Array(memory.buffer, start, size);
1887
+ new Uint8Array(memory.buffer, storagePtr, size).set(data);
1888
+ return data.byteLength;
1889
+ }
1890
+
1891
+ function _lowerFlatResult(lowerMetas) {
1892
+ const invalidTag = lowerMetas.find(t => t.tag !== 'ok' && t.tag !== 'error')
1893
+ if (invalidTag) { throw new Error(`invalid variant tag [${invalidTag}] found for result`); }
1612
1894
 
1613
- if (currentCallerTask.componentIdx() !== callerInstanceIdx) {
1614
- throw new Error(`task component idx [${ currentCallerTask.componentIdx() }] !== [${ callerInstanceIdx }] (callee ${ calleeInstanceIdx })`);
1895
+ return function _lowerFlatResultInner() {
1896
+ _debugLog('[_lowerFlatResult()] args', { lowerMetas });
1897
+ let lowerFn = _lowerFlatVariant({ discriminantSizeBytes: 1, lowerMetas }, { forResult: true });
1898
+ return lowerFn.apply(null, arguments);
1899
+ };
1900
+ }
1901
+
1902
+ function _lowerFlatOwn(size, memory, vals, storagePtr, storageLen) {
1903
+ _debugLog('[_lowerFlatOwn()] args', { size, memory, vals, storagePtr, storageLen });
1904
+ throw new Error('flat lower for owned resources not yet implemented!');
1905
+ }
1906
+ const ASYNC_STATE = new Map();
1907
+
1908
+ function getOrCreateAsyncState(componentIdx, init) {
1909
+ if (!ASYNC_STATE.has(componentIdx)) {
1910
+ const newState = new ComponentAsyncState({ componentIdx });
1911
+ ASYNC_STATE.set(componentIdx, newState);
1615
1912
  }
1913
+ return ASYNC_STATE.get(componentIdx);
1914
+ }
1915
+
1916
+ class ComponentAsyncState {
1917
+ static EVENT_HANDLER_EVENTS = [ 'backpressure-change' ];
1616
1918
 
1617
- let getCalleeParamsFn;
1618
- let resultPtr = null;
1619
- if (hasResultPointer) {
1620
- const directParamsArr = argArray.slice(11);
1621
- getCalleeParamsFn = () => directParamsArr;
1622
- resultPtr = argArray[10];
1623
- } else {
1624
- const directParamsArr = argArray.slice(10);
1625
- getCalleeParamsFn = () => directParamsArr;
1626
- }
1919
+ #componentIdx;
1920
+ #callingAsyncImport = false;
1921
+ #syncImportWait = promiseWithResolvers();
1922
+ #locked = false;
1923
+ #parkedTasks = new Map();
1924
+ #suspendedTasksByTaskID = new Map();
1925
+ #suspendedTaskIDs = [];
1926
+ #pendingTasks = [];
1927
+ #errored = null;
1627
1928
 
1628
- let encoding;
1629
- switch (stringEncoding) {
1630
- case 0:
1631
- encoding = 'utf8';
1632
- break;
1633
- case 1:
1634
- encoding = 'utf16';
1635
- break;
1636
- case 2:
1637
- encoding = 'compact-utf16';
1638
- break;
1639
- default:
1640
- throw new Error(`unrecognized string encoding enum [${stringEncoding}]`);
1641
- }
1929
+ #backpressure = 0;
1930
+ #backpressureWaiters = 0n;
1642
1931
 
1643
- const [newTask, newTaskID] = createNewCurrentTask({
1644
- componentIdx: calleeInstanceIdx,
1645
- isAsync: isCalleeAsyncInt !== 0,
1646
- getCalleeParamsFn,
1647
- // TODO: find a way to pass the import name through here
1648
- entryFnName: 'task/' + currentCallerTask.id() + '/new-prepare-task',
1649
- stringEncoding,
1650
- });
1932
+ #handlerMap = new Map();
1933
+ #nextHandlerID = 0n;
1651
1934
 
1652
- const subtask = currentCallerTask.createSubtask({
1653
- componentIdx: callerInstanceIdx,
1654
- parentTask: currentCallerTask,
1655
- childTask: newTask,
1656
- callMetadata: {
1657
- memory: getMemoryFn(),
1658
- memoryIdx,
1659
- resultPtr,
1660
- returnFn,
1661
- startFn,
1662
- }
1663
- });
1935
+ mayLeave = true;
1664
1936
 
1665
- newTask.setParentSubtask(subtask);
1666
- // NOTE: This isn't really a return memory idx for the caller, it's for checking
1667
- // against the task.return (which will be called from the callee)
1668
- newTask.setReturnMemoryIdx(memoryIdx);
1669
- }
1670
-
1671
- function _asyncStartCall(args, callee, paramCount, resultCount, flags) {
1672
- const { getCallbackFn, callbackIdx, getPostReturnFn, postReturnIdx } = args;
1673
- _debugLog('[_asyncStartCall()] args', args);
1937
+ #streams;
1674
1938
 
1675
- const taskMeta = getCurrentTask(ASYNC_CURRENT_COMPONENT_IDXS.at(-1), ASYNC_CURRENT_TASK_IDS.at(-1));
1676
- if (!taskMeta) { throw new Error('invalid/missing current async task meta during prepare call'); }
1939
+ waitableSets;
1940
+ waitables;
1941
+ subtasks;
1677
1942
 
1678
- const argArray = [...arguments];
1943
+ constructor(args) {
1944
+ this.#componentIdx = args.componentIdx;
1945
+ this.waitableSets = new RepTable({ target: `component [${this.#componentIdx}] waitable sets` });
1946
+ this.waitables = new RepTable({ target: `component [${this.#componentIdx}] waitables` });
1947
+ this.subtasks = new RepTable({ target: `component [${this.#componentIdx}] subtasks` });
1948
+ this.#streams = new Map();
1949
+ };
1679
1950
 
1680
- // NOTE: at this point we know the current task is the one that was started
1681
- // in PrepareCall, so we *should* be able to pop it back off and be left with
1682
- // the previous task
1683
- const preparedTask = taskMeta.task;
1684
- if (!preparedTask) { throw new Error('unexpectedly missing task in task meta during prepare call'); }
1951
+ componentIdx() { return this.#componentIdx; }
1952
+ streams() { return this.#streams; }
1685
1953
 
1686
- if (resultCount < 0 || resultCount > 1) { throw new Error('invalid/unsupported result count'); }
1954
+ errored() { return this.#errored !== null; }
1955
+ setErrored(err) {
1956
+ _debugLog('[ComponentAsyncState#setErrored()] component errored', { err, componentIdx: this.#componentIdx });
1957
+ if (this.#errored) { return; }
1958
+ if (!err) {
1959
+ err = new Error('error elswehere (see other component instance error)')
1960
+ err.componentIdx = this.#componentIdx;
1961
+ }
1962
+ this.#errored = err;
1963
+ }
1687
1964
 
1688
- const callbackFnName = 'callback_' + callbackIdx;
1689
- const callbackFn = getCallbackFn();
1690
- preparedTask.setCallbackFn(callbackFn, callbackFnName);
1691
- preparedTask.setPostReturnFn(getPostReturnFn());
1965
+ callingSyncImport(val) {
1966
+ if (val === undefined) { return this.#callingAsyncImport; }
1967
+ if (typeof val !== 'boolean') { throw new TypeError('invalid setting for async import'); }
1968
+ const prev = this.#callingAsyncImport;
1969
+ this.#callingAsyncImport = val;
1970
+ if (prev === true && this.#callingAsyncImport === false) {
1971
+ this.#notifySyncImportEnd();
1972
+ }
1973
+ }
1692
1974
 
1693
- const subtask = preparedTask.getParentSubtask();
1975
+ #notifySyncImportEnd() {
1976
+ const existing = this.#syncImportWait;
1977
+ this.#syncImportWait = promiseWithResolvers();
1978
+ existing.resolve();
1979
+ }
1694
1980
 
1695
- if (resultCount < 0 || resultCount > 1) { throw new Error(`unsupported result count [${ resultCount }]`); }
1981
+ async waitForSyncImportCallEnd() {
1982
+ await this.#syncImportWait.promise;
1983
+ }
1696
1984
 
1697
- const params = preparedTask.getCalleeParams();
1698
- if (paramCount !== params.length) {
1699
- throw new Error(`unexpected callee param count [${ params.length }], _asyncStartCall invocation expected [${ paramCount }]`);
1985
+ setBackpressure(v) { this.#backpressure = v; }
1986
+ getBackpressure(v) { return this.#backpressure; }
1987
+ incrementBackpressure() {
1988
+ const newValue = this.getBackpressure() + 1;
1989
+ if (newValue > 2**16) { throw new Error("invalid backpressure value, overflow"); }
1990
+ this.setBackpressure(newValue);
1700
1991
  }
1992
+ decrementBackpressure() {
1993
+ this.setBackpressure(Math.max(0, this.getBackpressure() - 1));
1994
+ }
1995
+ hasBackpressure() { return this.#backpressure > 0; }
1701
1996
 
1702
- subtask.setOnProgressFn(() => {
1703
- subtask.setPendingEventFn(() => {
1704
- if (subtask.resolved()) { subtask.deliverResolve(); }
1705
- return {
1706
- code: ASYNC_EVENT_CODE.SUBTASK,
1707
- index: rep,
1708
- result: subtask.getStateNumber(),
1997
+ waitForBackpressure() {
1998
+ let backpressureCleared = false;
1999
+ const cstate = this;
2000
+ cstate.addBackpressureWaiter();
2001
+ const handlerID = this.registerHandler({
2002
+ event: 'backpressure-change',
2003
+ fn: (bp) => {
2004
+ if (bp === 0) {
2005
+ cstate.removeHandler(handlerID);
2006
+ backpressureCleared = true;
2007
+ }
1709
2008
  }
1710
2009
  });
1711
- });
2010
+ return new Promise((resolve) => {
2011
+ const interval = setInterval(() => {
2012
+ if (backpressureCleared) { return; }
2013
+ clearInterval(interval);
2014
+ cstate.removeBackpressureWaiter();
2015
+ resolve(null);
2016
+ }, 0);
2017
+ });
2018
+ }
1712
2019
 
1713
- const subtaskState = subtask.getStateNumber();
1714
- if (subtaskState < 0 || subtaskState > 2**5) {
1715
- throw new Error('invalid subtask state, out of valid range');
2020
+ registerHandler(args) {
2021
+ const { event, fn } = args;
2022
+ if (!event) { throw new Error("missing handler event"); }
2023
+ if (!fn) { throw new Error("missing handler fn"); }
2024
+
2025
+ if (!ComponentAsyncState.EVENT_HANDLER_EVENTS.includes(event)) {
2026
+ throw new Error(`unrecognized event handler [${event}]`);
2027
+ }
2028
+
2029
+ const handlerID = this.#nextHandlerID++;
2030
+ let handlers = this.#handlerMap.get(event);
2031
+ if (!handlers) {
2032
+ handlers = [];
2033
+ this.#handlerMap.set(event, handlers)
2034
+ }
2035
+
2036
+ handlers.push({ id: handlerID, fn, event });
2037
+ return handlerID;
1716
2038
  }
1717
2039
 
1718
- const callerComponentState = getOrCreateAsyncState(subtask.componentIdx());
1719
- const rep = callerComponentState.subtasks.insert(subtask);
1720
- subtask.setRep(rep);
2040
+ removeHandler(args) {
2041
+ const { event, handlerID } = args;
2042
+ const registeredHandlers = this.#handlerMap.get(event);
2043
+ if (!registeredHandlers) { return; }
2044
+ const found = registeredHandlers.find(h => h.id === handlerID);
2045
+ if (!found) { return; }
2046
+ this.#handlerMap.set(event, this.#handlerMap.get(event).filter(h => h.id !== handlerID));
2047
+ }
1721
2048
 
1722
- const calleeComponentState = getOrCreateAsyncState(preparedTask.componentIdx());
1723
- const calleeBackpressure = calleeComponentState.hasBackpressure();
2049
+ getBackpressureWaiters() { return this.#backpressureWaiters; }
2050
+ addBackpressureWaiter() { this.#backpressureWaiters++; }
2051
+ removeBackpressureWaiter() {
2052
+ this.#backpressureWaiters--;
2053
+ if (this.#backpressureWaiters < 0) {
2054
+ throw new Error("unexepctedly negative number of backpressure waiters");
2055
+ }
2056
+ }
1724
2057
 
1725
- // Set up a handler on subtask completion to lower results from the call into the caller's memory region.
1726
- //
1727
- // NOTE: during fused guest->guest calls this handler is triggered, but does not actually perform
1728
- // lowering manually, as fused modules provider helper functions that can
1729
- subtask.registerOnResolveHandler((res) => {
1730
- _debugLog('[_asyncStartCall()] handling subtask result', { res, subtaskID: subtask.id() });
1731
- let subtaskCallMeta = subtask.getCallMetadata();
2058
+ parkTaskOnAwaitable(args) {
2059
+ if (!args.awaitable) { throw new TypeError('missing awaitable when trying to park'); }
2060
+ if (!args.task) { throw new TypeError('missing task when trying to park'); }
2061
+ const { awaitable, task } = args;
1732
2062
 
1733
- // NOTE: in the case of guest -> guest async calls, there may be no memory/realloc present,
1734
- // as the host will intermediate the value storage/movement between calls.
1735
- //
1736
- // We can simply take the value and lower it as a parameter
1737
- if (subtaskCallMeta.memory || subtaskCallMeta.realloc) {
1738
- throw new Error("call metadata unexpectedly contains memory/realloc for guest->guest call");
2063
+ let taskList = this.#parkedTasks.get(awaitable.id());
2064
+ if (!taskList) {
2065
+ taskList = [];
2066
+ this.#parkedTasks.set(awaitable.id(), taskList);
1739
2067
  }
2068
+ taskList.push(task);
1740
2069
 
1741
- const callerTask = subtask.getParentTask();
1742
- const calleeTask = preparedTask;
1743
- const callerMemoryIdx = callerTask.getReturnMemoryIdx();
1744
- const callerComponentIdx = callerTask.componentIdx();
1745
-
1746
- // If a helper function was provided we are likely in a fused guest->guest call,
1747
- // and the result will be delivered (lift/lowered) via helper function
1748
- if (subtaskCallMeta.returnFn) {
1749
- _debugLog('[_asyncStartCall()] return function present while ahndling subtask result, returning early (skipping lower)');
1750
- return;
1751
- }
2070
+ this.wakeNextTaskForAwaitable(awaitable);
2071
+ }
2072
+
2073
+ wakeNextTaskForAwaitable(awaitable) {
2074
+ if (!awaitable) { throw new TypeError('missing awaitable when waking next task'); }
2075
+ const awaitableID = awaitable.id();
1752
2076
 
1753
- // If there is no where to lower the results, exit early
1754
- if (!subtaskCallMeta.resultPtr) {
1755
- _debugLog('[_asyncStartCall()] no result ptr during subtask result handling, returning early (skipping lower)');
2077
+ const taskList = this.#parkedTasks.get(awaitableID);
2078
+ if (!taskList || taskList.length === 0) {
2079
+ _debugLog('[ComponentAsyncState] no tasks waiting for awaitable', { awaitableID: awaitable.id() });
1756
2080
  return;
1757
2081
  }
1758
2082
 
1759
- let callerMemory;
1760
- if (callerMemoryIdx) {
1761
- callerMemory = GlobalComponentMemories.getMemory(callerComponentIdx, callerMemoryIdx);
1762
- } else {
1763
- const callerMemories = GlobalComponentMemories.getMemoriesForComponentIdx(callerComponentIdx);
1764
- if (callerMemories.length != 1) { throw new Error(`unsupported amount of caller memories`); }
1765
- callerMemory = callerMemories[0];
1766
- }
2083
+ let task = taskList.shift(); // todo(perf)
2084
+ if (!task) { throw new Error('no task in parked list despite previous check'); }
1767
2085
 
1768
- if (!callerMemory) {
1769
- throw new Error(`missing memory for to guest->guest call result (subtask [${subtask.id()}])`);
2086
+ if (!task.awaitableResume) {
2087
+ throw new Error('task ready due to awaitable is missing resume', { taskID: task.id(), awaitableID });
1770
2088
  }
2089
+ task.awaitableResume();
2090
+ }
2091
+
2092
+ // TODO: we might want to check for pre-locked status here
2093
+ exclusiveLock() {
2094
+ this.#locked = true;
2095
+ }
2096
+
2097
+ exclusiveRelease() {
2098
+ _debugLog('[ComponentAsyncState#exclusiveRelease()] releasing', {
2099
+ locked: this.#locked,
2100
+ componentIdx: this.#componentIdx,
2101
+ });
1771
2102
 
1772
- const lowerFns = calleeTask.getReturnLowerFns();
1773
- if (!lowerFns || lowerFns.length === 0) {
1774
- throw new Error(`missing result lower metadata for guest->guests call (subtask [${subtask.id()}])`);
2103
+ this.#locked = false
2104
+ }
2105
+
2106
+ isExclusivelyLocked() { return this.#locked === true; }
2107
+
2108
+ #getSuspendedTaskMeta(taskID) {
2109
+ return this.#suspendedTasksByTaskID.get(taskID);
2110
+ }
2111
+
2112
+ #removeSuspendedTaskMeta(taskID) {
2113
+ _debugLog('[ComponentAsyncState#removeSuspendedTaskMeta()] removing suspended task', { taskID });
2114
+ const idx = this.#suspendedTaskIDs.findIndex(t => t === taskID);
2115
+ const meta = this.#suspendedTasksByTaskID.get(taskID);
2116
+ this.#suspendedTaskIDs[idx] = null;
2117
+ this.#suspendedTasksByTaskID.delete(taskID);
2118
+ return meta;
2119
+ }
2120
+
2121
+ #addSuspendedTaskMeta(meta) {
2122
+ if (!meta) { throw new Error('missing task meta'); }
2123
+ const taskID = meta.taskID;
2124
+ this.#suspendedTasksByTaskID.set(taskID, meta);
2125
+ this.#suspendedTaskIDs.push(taskID);
2126
+ if (this.#suspendedTasksByTaskID.size < this.#suspendedTaskIDs.length - 10) {
2127
+ this.#suspendedTaskIDs = this.#suspendedTaskIDs.filter(t => t !== null);
1775
2128
  }
2129
+ }
2130
+
2131
+ suspendTask(args) {
2132
+ // TODO(threads): readyFn is normally on the thread
2133
+ const { task, readyFn } = args;
2134
+ const taskID = task.id();
2135
+ _debugLog('[ComponentAsyncState#suspendTask()]', { taskID });
1776
2136
 
1777
- if (lowerFns.length !== 1) {
1778
- throw new Error(`only single result supported for guest->guest calls (subtask [${subtask.id()}])`);
2137
+ if (this.#getSuspendedTaskMeta(taskID)) {
2138
+ throw new Error('task [' + taskID + '] already suspended');
1779
2139
  }
1780
2140
 
1781
- lowerFns[0]({
1782
- realloc: undefined,
1783
- memory: callerMemory,
1784
- vals: [res],
1785
- storagePtr: subtaskCallMeta.resultPtr,
1786
- componentIdx: callerComponentIdx
2141
+ const { promise, resolve } = Promise.withResolvers();
2142
+ this.#addSuspendedTaskMeta({
2143
+ task,
2144
+ taskID,
2145
+ readyFn,
2146
+ resume: () => {
2147
+ _debugLog('[ComponentAsyncState#suspendTask()] resuming suspended task', { taskID });
2148
+ // TODO(threads): it's thread cancellation we should be checking for below, not task
2149
+ resolve(!task.isCancelled());
2150
+ },
1787
2151
  });
1788
2152
 
1789
- });
1790
-
1791
- // Build call params
1792
- const subtaskCallMeta = subtask.getCallMetadata();
1793
- let startFnParams = [];
1794
- let calleeParams = [];
1795
- if (subtaskCallMeta.startFn && subtaskCallMeta.resultPtr) {
1796
- // If we're using a fused component start fn and a result pointer is present,
1797
- // then we need to pass the result pointer and other params to the start fn
1798
- startFnParams.push(subtaskCallMeta.resultPtr, ...params);
1799
- } else {
1800
- // if not we need to pass params to the callee instead
1801
- startFnParams.push(...params);
1802
- calleeParams.push(...params);
2153
+ return promise;
1803
2154
  }
1804
2155
 
1805
- preparedTask.registerOnResolveHandler((res) => {
1806
- _debugLog('[_asyncStartCall()] signaling subtask completion due to task completion', {
1807
- childTaskID: preparedTask.id(),
1808
- subtaskID: subtask.id(),
1809
- parentTaskID: subtask.getParentTask().id(),
1810
- });
1811
- subtask.onResolve(res);
1812
- });
1813
-
1814
- // TODO(fix): start fns sometimes produce results, how should they be used?
1815
- // the result should theoretically be used for flat lowering, but fused components do
1816
- // this automatically!
1817
- subtask.onStart({ startFnParams });
1818
-
1819
- _debugLog("[_asyncStartCall()] initial call", {
1820
- task: preparedTask.id(),
1821
- subtaskID: subtask.id(),
1822
- calleeFnName: callee.name,
1823
- });
2156
+ resumeTaskByID(taskID) {
2157
+ const meta = this.#removeSuspendedTaskMeta(taskID);
2158
+ if (!meta) { return; }
2159
+ if (meta.taskID !== taskID) { throw new Error('task ID does not match'); }
2160
+ meta.resume();
2161
+ }
1824
2162
 
1825
- const callbackResult = callee.apply(null, calleeParams);
2163
+ tick() {
2164
+ _debugLog('[ComponentAsyncState#tick()]', { suspendedTaskIDs: this.#suspendedTaskIDs });
2165
+ const resumableTasks = this.#suspendedTaskIDs.filter(t => t !== null);
2166
+ for (const taskID of resumableTasks) {
2167
+ const meta = this.#suspendedTasksByTaskID.get(taskID);
2168
+ if (!meta || !meta.readyFn) {
2169
+ throw new Error(`missing/invalid task despite ID [${taskID}] being present`);
2170
+ }
2171
+
2172
+ const isReady = meta.readyFn();
2173
+ if (!isReady) { continue; }
2174
+
2175
+ this.resumeTaskByID(taskID);
2176
+ }
2177
+
2178
+ return this.#suspendedTaskIDs.filter(t => t !== null).length === 0;
2179
+ }
1826
2180
 
1827
- _debugLog("[_asyncStartCall()] after initial call", {
1828
- task: preparedTask.id(),
1829
- subtaskID: subtask.id(),
1830
- calleeFnName: callee.name,
1831
- });
2181
+ addPendingTask(task) {
2182
+ this.#pendingTasks.push(task);
2183
+ }
1832
2184
 
1833
- const doSubtaskResolve = () => {
1834
- subtask.deliverResolve();
1835
- };
2185
+ addStreamEnd(args) {
2186
+ _debugLog('[ComponentAsyncState#addStreamEnd()] args', args);
2187
+ const { tableIdx, streamEnd } = args;
2188
+
2189
+ let tbl = this.#streams.get(tableIdx);
2190
+ if (!tbl) {
2191
+ tbl = new RepTable({ target: `component [${this.#componentIdx}] streams` });
2192
+ this.#streams.set(tableIdx, tbl);
2193
+ }
2194
+
2195
+ const streamIdx = tbl.insert(streamEnd);
2196
+ return streamIdx;
2197
+ }
1836
2198
 
1837
- // If a single call resolved the subtask and there is no backpressure in the guest,
1838
- // we can return immediately
1839
- if (subtask.resolved() && !calleeBackpressure) {
1840
- _debugLog("[_asyncStartCall()] instantly resolved", {
1841
- calleeComponentIdx: preparedTask.componentIdx(),
1842
- task: preparedTask.id(),
1843
- subtaskID: subtask.id(),
1844
- callerComponentIdx: subtask.componentIdx(),
1845
- });
2199
+ createStream(args) {
2200
+ _debugLog('[ComponentAsyncState#createStream()] args', args);
2201
+ const { tableIdx, elemMeta } = args;
2202
+ if (tableIdx === undefined) { throw new Error("missing table idx while adding stream"); }
2203
+ if (elemMeta === undefined) { throw new Error("missing element metadata while adding stream"); }
1846
2204
 
1847
- // If a fused component return function was specified for the subtask,
1848
- // we've likely already called it during resolution of the task.
1849
- //
1850
- // In this case, we do not want to actually return 2 AKA "RETURNED",
1851
- // but the normal started task state, because the fused component expects to get
1852
- // the waitable + the original subtask state (0 AKA "STARTING")
1853
- //
1854
- if (subtask.getCallMetadata().returnFn) {
1855
- return Number(subtask.waitableRep()) << 4 | subtaskState;
2205
+ let tbl = this.#streams.get(tableIdx);
2206
+ if (!tbl) {
2207
+ tbl = new RepTable({ target: `component [${this.#componentIdx}] streams` });
2208
+ this.#streams.set(tableIdx, tbl);
1856
2209
  }
1857
2210
 
1858
- doSubtaskResolve();
1859
- return AsyncSubtask.State.RETURNED;
2211
+ const stream = new InternalStream({
2212
+ tableIdx,
2213
+ componentIdx: this.#componentIdx,
2214
+ elemMeta,
2215
+ });
2216
+ const writeEndIdx = tbl.insert(stream.getWriteEnd());
2217
+ stream.setWriteEndIdx(writeEndIdx);
2218
+ const readEndIdx = tbl.insert(stream.getReadEnd());
2219
+ stream.setReadEndIdx(readEndIdx);
2220
+
2221
+ const rep = STREAMS.insert(stream);
2222
+ stream.setRep(rep);
2223
+
2224
+ return { writeEndIdx, readEndIdx };
1860
2225
  }
1861
2226
 
1862
- // Start the (event) driver loop that will resolve the task
1863
- new Promise(async (resolve, reject) => {
1864
- if (subtask.resolved() && calleeBackpressure) {
1865
- await calleeComponentState.waitForBackpressure();
1866
-
1867
- _debugLog("[_asyncStartCall()] instantly resolved after cleared backpressure", {
1868
- calleeComponentIdx: preparedTask.componentIdx(),
1869
- task: preparedTask.id(),
1870
- subtaskID: subtask.id(),
1871
- callerComponentIdx: subtask.componentIdx(),
1872
- });
1873
- return;
2227
+ getStreamEnd(args) {
2228
+ _debugLog('[ComponentAsyncState#getStreamEnd()] args', args);
2229
+ const { tableIdx, streamIdx } = args;
2230
+ if (tableIdx === undefined) { throw new Error('missing table idx while retrieveing stream end'); }
2231
+ if (streamIdx === undefined) { throw new Error('missing stream idx while retrieveing stream end'); }
2232
+
2233
+ const tbl = this.#streams.get(tableIdx);
2234
+ if (!tbl) {
2235
+ throw new Error(`missing stream table [${tableIdx}] in component [${this.#componentIdx}] while getting stream`);
1874
2236
  }
1875
2237
 
1876
- const started = await preparedTask.enter();
1877
- if (!started) {
1878
- _debugLog('[_asyncStartCall()] task failed early', {
1879
- taskID: preparedTask.id(),
1880
- subtaskID: subtask.id(),
1881
- });
1882
- throw new Error("task failed to start");
1883
- return;
2238
+ const stream = tbl.get(streamIdx);
2239
+ return stream;
2240
+ }
2241
+
2242
+ removeStreamEnd(args) {
2243
+ _debugLog('[ComponentAsyncState#removeStreamEnd()] args', args);
2244
+ const { tableIdx, streamIdx } = args;
2245
+ if (tableIdx === undefined) { throw new Error("missing table idx while removing stream end"); }
2246
+ if (streamIdx === undefined) { throw new Error("missing stream idx while removing stream end"); }
2247
+
2248
+ const tbl = this.#streams.get(tableIdx);
2249
+ if (!tbl) {
2250
+ throw new Error(`missing stream table [${tableIdx}] in component [${this.#componentIdx}] while removing stream end`);
1884
2251
  }
1885
2252
 
1886
- // TODO: retrieve/pass along actual fn name the callback corresponds to
1887
- // (at least something like `<lifted fn name>_callback`)
1888
- const fnName = [
1889
- '<task ',
1890
- subtask.parentTaskID(),
1891
- '/subtask ',
1892
- subtask.id(),
1893
- '/task ',
1894
- preparedTask.id(),
1895
- '>',
1896
- ].join("");
2253
+ const stream = tbl.get(streamIdx);
2254
+ if (!stream) { throw new Error(`component [${this.#componentIdx}] missing stream [${streamIdx}]`); }
1897
2255
 
1898
- try {
1899
- _debugLog("[_asyncStartCall()] starting driver loop", { fnName, componentIdx: preparedTask.componentIdx(), });
1900
- await _driverLoop({
1901
- componentState: calleeComponentState,
1902
- task: preparedTask,
1903
- fnName,
1904
- isAsync: true,
1905
- callbackResult,
1906
- resolve,
1907
- reject
1908
- });
1909
- } catch (err) {
1910
- _debugLog("[AsyncStartCall] drive loop call failure", { err });
2256
+ const removed = tbl.remove(streamIdx);
2257
+ if (!removed) {
2258
+ throw new Error(`missing stream [${streamIdx}] (table [${tableIdx}]) in component [${this.#componentIdx}] while removing stream end`);
1911
2259
  }
1912
2260
 
1913
- });
1914
-
1915
- return Number(subtask.waitableRep()) << 4 | subtaskState;
1916
- }
1917
-
1918
- function _syncStartCall(callbackIdx) {
1919
- _debugLog('[_syncStartCall()] args', { callbackIdx });
1920
- throw new Error('synchronous start call not implemented!');
1921
- }
1922
-
1923
- function promiseWithResolvers() {
1924
- if (Promise.withResolvers) {
1925
- return Promise.withResolvers();
1926
- } else {
1927
- let resolve;
1928
- let reject;
1929
- const promise = new Promise((res, rej) => {
1930
- resolve = res;
1931
- reject = rej;
1932
- });
1933
- return { promise, resolve, reject };
2261
+ return stream;
1934
2262
  }
1935
2263
  }
1936
2264
 
1937
- const _debugLog = (...args) => {
1938
- if (!globalThis?.process?.env?.JCO_DEBUG) { return; }
1939
- console.debug(...args);
1940
- }
1941
- const ASYNC_DETERMINISM = 'random';
1942
- const _coinFlip = () => { return Math.random() > 0.5; };
1943
- const I32_MAX = 2_147_483_647;
1944
- const I32_MIN = -2_147_483_648;
1945
- const _typeCheckValidI32 = (n) => typeof n === 'number' && n >= I32_MIN && n <= I32_MAX;
1946
-
1947
- const _typeCheckAsyncFn= (f) => {
1948
- return f instanceof ASYNC_FN_CTOR;
1949
- };
1950
-
1951
- const ASYNC_FN_CTOR = (async () => {}).constructor;
1952
-
1953
2265
  const base64Compile = str => WebAssembly.compile(typeof Buffer !== 'undefined' ? Buffer.from(str, 'base64') : Uint8Array.from(atob(str), b => b.charCodeAt(0)));
1954
2266
 
1955
2267
  const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
@@ -1986,62 +2298,6 @@ function getErrorPayload(e) {
1986
2298
  return e;
1987
2299
  }
1988
2300
 
1989
- class RepTable {
1990
- #data = [0, null];
1991
- #target;
1992
-
1993
- constructor(args) {
1994
- if (args?.target) { this.target = args.target }
1995
- }
1996
-
1997
- insert(val) {
1998
- _debugLog('[RepTable#insert()] args', { val, target: this.target });
1999
- const freeIdx = this.#data[0];
2000
- if (freeIdx === 0) {
2001
- this.#data.push(val);
2002
- this.#data.push(null);
2003
- return (this.#data.length >> 1) - 1;
2004
- }
2005
- this.#data[0] = this.#data[freeIdx << 1];
2006
- const placementIdx = freeIdx << 1;
2007
- this.#data[placementIdx] = val;
2008
- this.#data[placementIdx + 1] = null;
2009
- return freeIdx;
2010
- }
2011
-
2012
- get(rep) {
2013
- _debugLog('[RepTable#get()] args', { rep, target: this.target });
2014
- const baseIdx = rep << 1;
2015
- const val = this.#data[baseIdx];
2016
- return val;
2017
- }
2018
-
2019
- contains(rep) {
2020
- _debugLog('[RepTable#contains()] args', { rep, target: this.target });
2021
- const baseIdx = rep << 1;
2022
- return !!this.#data[baseIdx];
2023
- }
2024
-
2025
- remove(rep) {
2026
- _debugLog('[RepTable#remove()] args', { rep, target: this.target });
2027
- if (this.#data.length === 2) { throw new Error('invalid'); }
2028
-
2029
- const baseIdx = rep << 1;
2030
- const val = this.#data[baseIdx];
2031
- if (val === 0) { throw new Error('invalid resource rep (cannot be 0)'); }
2032
-
2033
- this.#data[baseIdx] = this.#data[0];
2034
- this.#data[0] = rep;
2035
-
2036
- return val;
2037
- }
2038
-
2039
- clear() {
2040
- _debugLog('[RepTable#clear()] args', { rep, target: this.target });
2041
- this.#data = [0, null];
2042
- }
2043
- }
2044
-
2045
2301
  function throwUninitialized() {
2046
2302
  throw new TypeError('Wasm uninitialized use `await $init` first');
2047
2303
  }
@@ -2050,65 +2306,6 @@ const hasOwnProperty = Object.prototype.hasOwnProperty;
2050
2306
 
2051
2307
  const instantiateCore = WebAssembly.instantiate;
2052
2308
 
2053
- class GlobalComponentAsyncLowers {
2054
- static map = new Map();
2055
-
2056
- constructor() { throw new Error('GlobalComponentAsyncLowers should not be constructed'); }
2057
-
2058
- static define(args) {
2059
- const { componentIdx, qualifiedImportFn, fn } = args;
2060
- let inner = GlobalComponentAsyncLowers.map.get(componentIdx);
2061
- if (!inner) {
2062
- inner = new Map();
2063
- GlobalComponentAsyncLowers.map.set(componentIdx, inner);
2064
- }
2065
-
2066
- inner.set(qualifiedImportFn, fn);
2067
- }
2068
-
2069
- static lookup(componentIdx, qualifiedImportFn) {
2070
- let inner = GlobalComponentAsyncLowers.map.get(componentIdx);
2071
- if (!inner) {
2072
- inner = new Map();
2073
- GlobalComponentAsyncLowers.map.set(componentIdx, inner);
2074
- }
2075
-
2076
- const found = inner.get(qualifiedImportFn);
2077
- if (found) { return found; }
2078
-
2079
- return (...args) => {
2080
- const [originalFn, ...params] = args;
2081
- return originalFn(...params);
2082
- };
2083
- }
2084
- }
2085
-
2086
- class GlobalComponentMemories {
2087
- static map = new Map();
2088
-
2089
- constructor() { throw new Error('GlobalComponentMemories should not be constructed'); }
2090
-
2091
- static save(args) {
2092
- const { idx, componentIdx, memory } = args;
2093
- let inner = GlobalComponentMemories.map.get(componentIdx);
2094
- if (!inner) {
2095
- inner = [];
2096
- GlobalComponentMemories.map.set(componentIdx, inner);
2097
- }
2098
- inner.push({ memory, idx });
2099
- }
2100
-
2101
- static getMemoriesForComponentIdx(componentIdx) {
2102
- const metas = GlobalComponentMemories.map.get(componentIdx);
2103
- return metas.map(meta => meta.memory);
2104
- }
2105
-
2106
- static getMemory(componentIdx, idx) {
2107
- const metas = GlobalComponentMemories.map.get(componentIdx);
2108
- return metas.find(meta => meta.idx === idx)?.memory;
2109
- }
2110
- }
2111
-
2112
2309
 
2113
2310
  let exports0;
2114
2311
  let exports1;
@@ -2125,7 +2322,8 @@ handleTables[2] = handleTable2;
2125
2322
 
2126
2323
  function trampoline5() {
2127
2324
  _debugLog('[iface="wasi:cli/stderr@0.2.3", function="get-stderr"] [Instruction::CallInterface] (sync, @ enter)');
2128
- const hostProvided = getStderr._isHostProvided;
2325
+ let hostProvided = false;
2326
+ hostProvided = getStderr?._isHostProvided;
2129
2327
 
2130
2328
  let parentTask;
2131
2329
  let task;
@@ -2197,7 +2395,8 @@ handleTables[1] = handleTable1;
2197
2395
 
2198
2396
  function trampoline8() {
2199
2397
  _debugLog('[iface="wasi:cli/stdin@0.2.3", function="get-stdin"] [Instruction::CallInterface] (sync, @ enter)');
2200
- const hostProvided = getStdin._isHostProvided;
2398
+ let hostProvided = false;
2399
+ hostProvided = getStdin?._isHostProvided;
2201
2400
 
2202
2401
  let parentTask;
2203
2402
  let task;
@@ -2265,7 +2464,8 @@ let lowered_import_2_metadata = {
2265
2464
 
2266
2465
  function trampoline9() {
2267
2466
  _debugLog('[iface="wasi:cli/stdout@0.2.3", function="get-stdout"] [Instruction::CallInterface] (sync, @ enter)');
2268
- const hostProvided = getStdout._isHostProvided;
2467
+ let hostProvided = false;
2468
+ hostProvided = getStdout?._isHostProvided;
2269
2469
 
2270
2470
  let parentTask;
2271
2471
  let task;
@@ -2353,7 +2553,8 @@ function trampoline10(arg0) {
2353
2553
  }
2354
2554
  }
2355
2555
  _debugLog('[iface="wasi:cli/exit@0.2.3", function="exit"] [Instruction::CallInterface] (sync, @ enter)');
2356
- const hostProvided = exit._isHostProvided;
2556
+ let hostProvided = false;
2557
+ hostProvided = exit?._isHostProvided;
2357
2558
 
2358
2559
  let parentTask;
2359
2560
  let task;
@@ -2414,7 +2615,8 @@ let lowered_import_4_metadata = {
2414
2615
 
2415
2616
  function trampoline11(arg0) {
2416
2617
  _debugLog('[iface="wasi:cli/environment@0.2.3", function="get-environment"] [Instruction::CallInterface] (sync, @ enter)');
2417
- const hostProvided = getEnvironment._isHostProvided;
2618
+ let hostProvided = false;
2619
+ hostProvided = getEnvironment?._isHostProvided;
2418
2620
 
2419
2621
  let parentTask;
2420
2622
  let task;
@@ -2508,7 +2710,8 @@ function trampoline12(arg0, arg1) {
2508
2710
  }
2509
2711
  curResourceBorrows.push(rsc0);
2510
2712
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]descriptor.get-type"] [Instruction::CallInterface] (sync, @ enter)');
2511
- const hostProvided = rsc0.getType._isHostProvided;
2713
+ let hostProvided = false;
2714
+ hostProvided = rsc0.getType?._isHostProvided;
2512
2715
 
2513
2716
  let parentTask;
2514
2717
  let task;
@@ -2806,7 +3009,8 @@ function trampoline13(arg0, arg1) {
2806
3009
  }
2807
3010
  curResourceBorrows.push(rsc0);
2808
3011
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]descriptor.metadata-hash"] [Instruction::CallInterface] (sync, @ enter)');
2809
- const hostProvided = rsc0.metadataHash._isHostProvided;
3012
+ let hostProvided = false;
3013
+ hostProvided = rsc0.metadataHash?._isHostProvided;
2810
3014
 
2811
3015
  let parentTask;
2812
3016
  let task;
@@ -3067,7 +3271,8 @@ function trampoline14(arg0, arg1) {
3067
3271
  }
3068
3272
  curResourceBorrows.push(rsc0);
3069
3273
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="filesystem-error-code"] [Instruction::CallInterface] (sync, @ enter)');
3070
- const hostProvided = filesystemErrorCode._isHostProvided;
3274
+ let hostProvided = false;
3275
+ hostProvided = filesystemErrorCode?._isHostProvided;
3071
3276
 
3072
3277
  let parentTask;
3073
3278
  let task;
@@ -3314,7 +3519,8 @@ function trampoline15(arg0, arg1, arg2, arg3, arg4) {
3314
3519
  var len4 = arg3;
3315
3520
  var result4 = TEXT_DECODER_UTF8.decode(new Uint8Array(memory0.buffer, ptr4, len4));
3316
3521
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]descriptor.metadata-hash-at"] [Instruction::CallInterface] (sync, @ enter)');
3317
- const hostProvided = rsc0.metadataHashAt._isHostProvided;
3522
+ let hostProvided = false;
3523
+ hostProvided = rsc0.metadataHashAt?._isHostProvided;
3318
3524
 
3319
3525
  let parentTask;
3320
3526
  let task;
@@ -3571,7 +3777,8 @@ function trampoline16(arg0, arg1, arg2) {
3571
3777
  }
3572
3778
  curResourceBorrows.push(rsc0);
3573
3779
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]descriptor.read-via-stream"] [Instruction::CallInterface] (sync, @ enter)');
3574
- const hostProvided = rsc0.readViaStream._isHostProvided;
3780
+ let hostProvided = false;
3781
+ hostProvided = rsc0.readViaStream?._isHostProvided;
3575
3782
 
3576
3783
  let parentTask;
3577
3784
  let task;
@@ -3835,7 +4042,8 @@ function trampoline17(arg0, arg1, arg2) {
3835
4042
  }
3836
4043
  curResourceBorrows.push(rsc0);
3837
4044
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]descriptor.write-via-stream"] [Instruction::CallInterface] (sync, @ enter)');
3838
- const hostProvided = rsc0.writeViaStream._isHostProvided;
4045
+ let hostProvided = false;
4046
+ hostProvided = rsc0.writeViaStream?._isHostProvided;
3839
4047
 
3840
4048
  let parentTask;
3841
4049
  let task;
@@ -4099,7 +4307,8 @@ function trampoline18(arg0, arg1) {
4099
4307
  }
4100
4308
  curResourceBorrows.push(rsc0);
4101
4309
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]descriptor.append-via-stream"] [Instruction::CallInterface] (sync, @ enter)');
4102
- const hostProvided = rsc0.appendViaStream._isHostProvided;
4310
+ let hostProvided = false;
4311
+ hostProvided = rsc0.appendViaStream?._isHostProvided;
4103
4312
 
4104
4313
  let parentTask;
4105
4314
  let task;
@@ -4367,7 +4576,8 @@ function trampoline19(arg0, arg1) {
4367
4576
  }
4368
4577
  curResourceBorrows.push(rsc0);
4369
4578
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]descriptor.read-directory"] [Instruction::CallInterface] (sync, @ enter)');
4370
- const hostProvided = rsc0.readDirectory._isHostProvided;
4579
+ let hostProvided = false;
4580
+ hostProvided = rsc0.readDirectory?._isHostProvided;
4371
4581
 
4372
4582
  let parentTask;
4373
4583
  let task;
@@ -4631,7 +4841,8 @@ function trampoline20(arg0, arg1) {
4631
4841
  }
4632
4842
  curResourceBorrows.push(rsc0);
4633
4843
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]descriptor.stat"] [Instruction::CallInterface] (sync, @ enter)');
4634
- const hostProvided = rsc0.stat._isHostProvided;
4844
+ let hostProvided = false;
4845
+ hostProvided = rsc0.stat?._isHostProvided;
4635
4846
 
4636
4847
  let parentTask;
4637
4848
  let task;
@@ -4971,7 +5182,8 @@ function trampoline21(arg0, arg1, arg2, arg3, arg4) {
4971
5182
  var len4 = arg3;
4972
5183
  var result4 = TEXT_DECODER_UTF8.decode(new Uint8Array(memory0.buffer, ptr4, len4));
4973
5184
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]descriptor.stat-at"] [Instruction::CallInterface] (sync, @ enter)');
4974
- const hostProvided = rsc0.statAt._isHostProvided;
5185
+ let hostProvided = false;
5186
+ hostProvided = rsc0.statAt?._isHostProvided;
4975
5187
 
4976
5188
  let parentTask;
4977
5189
  let task;
@@ -5331,7 +5543,8 @@ function trampoline22(arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
5331
5543
  mutateDirectory: Boolean(arg5 & 32),
5332
5544
  };
5333
5545
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]descriptor.open-at"] [Instruction::CallInterface] (sync, @ enter)');
5334
- const hostProvided = rsc0.openAt._isHostProvided;
5546
+ let hostProvided = false;
5547
+ hostProvided = rsc0.openAt?._isHostProvided;
5335
5548
 
5336
5549
  let parentTask;
5337
5550
  let task;
@@ -5595,7 +5808,8 @@ function trampoline23(arg0, arg1) {
5595
5808
  }
5596
5809
  curResourceBorrows.push(rsc0);
5597
5810
  _debugLog('[iface="wasi:filesystem/types@0.2.3", function="[method]directory-entry-stream.read-directory-entry"] [Instruction::CallInterface] (sync, @ enter)');
5598
- const hostProvided = rsc0.readDirectoryEntry._isHostProvided;
5811
+ let hostProvided = false;
5812
+ hostProvided = rsc0.readDirectoryEntry?._isHostProvided;
5599
5813
 
5600
5814
  let parentTask;
5601
5815
  let task;
@@ -5908,7 +6122,8 @@ function trampoline24(arg0, arg1, arg2) {
5908
6122
  }
5909
6123
  curResourceBorrows.push(rsc0);
5910
6124
  _debugLog('[iface="wasi:io/streams@0.2.3", function="[method]input-stream.read"] [Instruction::CallInterface] (sync, @ enter)');
5911
- const hostProvided = rsc0.read._isHostProvided;
6125
+ let hostProvided = false;
6126
+ hostProvided = rsc0.read?._isHostProvided;
5912
6127
 
5913
6128
  let parentTask;
5914
6129
  let task;
@@ -5968,8 +6183,24 @@ function trampoline24(arg0, arg1, arg2) {
5968
6183
  var val3 = e;
5969
6184
  var len3 = val3.byteLength;
5970
6185
  var ptr3 = realloc0(0, 0, 1, len3 * 1);
5971
- var src3 = new Uint8Array(val3.buffer || val3, val3.byteOffset, len3 * 1);
5972
- (new Uint8Array(memory0.buffer, ptr3, len3 * 1)).set(src3);
6186
+
6187
+ let valData3;
6188
+ const valLenBytes3 = len3 * 1;
6189
+ if (Array.isArray(val3)) {
6190
+ // Regular array likely containing numbers, write values to memory
6191
+ let offset = 0;
6192
+ const dv3 = new DataView(memory0.buffer);
6193
+ for (const v of val3) {
6194
+ dv3.setUint8(ptr3+ offset, v, true);
6195
+ offset += 1;
6196
+ }
6197
+ } else {
6198
+ // TypedArray / ArrayBuffer-like, direct copy
6199
+ valData3 = new Uint8Array(val3.buffer || val3, val3.byteOffset, valLenBytes3);
6200
+ const out3 = new Uint8Array(memory0.buffer, ptr3,valLenBytes3);
6201
+ out3.set(valData3);
6202
+ }
6203
+
5973
6204
  dataView(memory0).setUint32(arg2 + 8, len3, true);
5974
6205
  dataView(memory0).setUint32(arg2 + 4, ptr3, true);
5975
6206
  break;
@@ -6034,7 +6265,8 @@ function trampoline25(arg0, arg1, arg2) {
6034
6265
  }
6035
6266
  curResourceBorrows.push(rsc0);
6036
6267
  _debugLog('[iface="wasi:io/streams@0.2.3", function="[method]input-stream.blocking-read"] [Instruction::CallInterface] (sync, @ enter)');
6037
- const hostProvided = rsc0.blockingRead._isHostProvided;
6268
+ let hostProvided = false;
6269
+ hostProvided = rsc0.blockingRead?._isHostProvided;
6038
6270
 
6039
6271
  let parentTask;
6040
6272
  let task;
@@ -6094,8 +6326,24 @@ function trampoline25(arg0, arg1, arg2) {
6094
6326
  var val3 = e;
6095
6327
  var len3 = val3.byteLength;
6096
6328
  var ptr3 = realloc0(0, 0, 1, len3 * 1);
6097
- var src3 = new Uint8Array(val3.buffer || val3, val3.byteOffset, len3 * 1);
6098
- (new Uint8Array(memory0.buffer, ptr3, len3 * 1)).set(src3);
6329
+
6330
+ let valData3;
6331
+ const valLenBytes3 = len3 * 1;
6332
+ if (Array.isArray(val3)) {
6333
+ // Regular array likely containing numbers, write values to memory
6334
+ let offset = 0;
6335
+ const dv3 = new DataView(memory0.buffer);
6336
+ for (const v of val3) {
6337
+ dv3.setUint8(ptr3+ offset, v, true);
6338
+ offset += 1;
6339
+ }
6340
+ } else {
6341
+ // TypedArray / ArrayBuffer-like, direct copy
6342
+ valData3 = new Uint8Array(val3.buffer || val3, val3.byteOffset, valLenBytes3);
6343
+ const out3 = new Uint8Array(memory0.buffer, ptr3,valLenBytes3);
6344
+ out3.set(valData3);
6345
+ }
6346
+
6099
6347
  dataView(memory0).setUint32(arg2 + 8, len3, true);
6100
6348
  dataView(memory0).setUint32(arg2 + 4, ptr3, true);
6101
6349
  break;
@@ -6160,7 +6408,8 @@ function trampoline26(arg0, arg1) {
6160
6408
  }
6161
6409
  curResourceBorrows.push(rsc0);
6162
6410
  _debugLog('[iface="wasi:io/streams@0.2.3", function="[method]output-stream.check-write"] [Instruction::CallInterface] (sync, @ enter)');
6163
- const hostProvided = rsc0.checkWrite._isHostProvided;
6411
+ let hostProvided = false;
6412
+ hostProvided = rsc0.checkWrite?._isHostProvided;
6164
6413
 
6165
6414
  let parentTask;
6166
6415
  let task;
@@ -6283,7 +6532,8 @@ function trampoline27(arg0, arg1, arg2, arg3) {
6283
6532
  var len3 = arg2;
6284
6533
  var result3 = new Uint8Array(memory0.buffer.slice(ptr3, ptr3 + len3 * 1));
6285
6534
  _debugLog('[iface="wasi:io/streams@0.2.3", function="[method]output-stream.write"] [Instruction::CallInterface] (sync, @ enter)');
6286
- const hostProvided = rsc0.write._isHostProvided;
6535
+ let hostProvided = false;
6536
+ hostProvided = rsc0.write?._isHostProvided;
6287
6537
 
6288
6538
  let parentTask;
6289
6539
  let task;
@@ -6405,7 +6655,8 @@ function trampoline28(arg0, arg1, arg2, arg3) {
6405
6655
  var len3 = arg2;
6406
6656
  var result3 = new Uint8Array(memory0.buffer.slice(ptr3, ptr3 + len3 * 1));
6407
6657
  _debugLog('[iface="wasi:io/streams@0.2.3", function="[method]output-stream.blocking-write-and-flush"] [Instruction::CallInterface] (sync, @ enter)');
6408
- const hostProvided = rsc0.blockingWriteAndFlush._isHostProvided;
6658
+ let hostProvided = false;
6659
+ hostProvided = rsc0.blockingWriteAndFlush?._isHostProvided;
6409
6660
 
6410
6661
  let parentTask;
6411
6662
  let task;
@@ -6524,7 +6775,8 @@ function trampoline29(arg0, arg1) {
6524
6775
  }
6525
6776
  curResourceBorrows.push(rsc0);
6526
6777
  _debugLog('[iface="wasi:io/streams@0.2.3", function="[method]output-stream.blocking-flush"] [Instruction::CallInterface] (sync, @ enter)');
6527
- const hostProvided = rsc0.blockingFlush._isHostProvided;
6778
+ let hostProvided = false;
6779
+ hostProvided = rsc0.blockingFlush?._isHostProvided;
6528
6780
 
6529
6781
  let parentTask;
6530
6782
  let task;
@@ -6634,7 +6886,8 @@ let lowered_import_23_metadata = {
6634
6886
 
6635
6887
  function trampoline30(arg0, arg1) {
6636
6888
  _debugLog('[iface="wasi:random/random@0.2.3", function="get-random-bytes"] [Instruction::CallInterface] (sync, @ enter)');
6637
- const hostProvided = getRandomBytes._isHostProvided;
6889
+ let hostProvided = false;
6890
+ hostProvided = getRandomBytes?._isHostProvided;
6638
6891
 
6639
6892
  let parentTask;
6640
6893
  let task;
@@ -6678,8 +6931,24 @@ function trampoline30(arg0, arg1) {
6678
6931
  var val0 = ret;
6679
6932
  var len0 = val0.byteLength;
6680
6933
  var ptr0 = realloc0(0, 0, 1, len0 * 1);
6681
- var src0 = new Uint8Array(val0.buffer || val0, val0.byteOffset, len0 * 1);
6682
- (new Uint8Array(memory0.buffer, ptr0, len0 * 1)).set(src0);
6934
+
6935
+ let valData0;
6936
+ const valLenBytes0 = len0 * 1;
6937
+ if (Array.isArray(val0)) {
6938
+ // Regular array likely containing numbers, write values to memory
6939
+ let offset = 0;
6940
+ const dv0 = new DataView(memory0.buffer);
6941
+ for (const v of val0) {
6942
+ dv0.setUint8(ptr0+ offset, v, true);
6943
+ offset += 1;
6944
+ }
6945
+ } else {
6946
+ // TypedArray / ArrayBuffer-like, direct copy
6947
+ valData0 = new Uint8Array(val0.buffer || val0, val0.byteOffset, valLenBytes0);
6948
+ const out0 = new Uint8Array(memory0.buffer, ptr0,valLenBytes0);
6949
+ out0.set(valData0);
6950
+ }
6951
+
6683
6952
  dataView(memory0).setUint32(arg1 + 4, len0, true);
6684
6953
  dataView(memory0).setUint32(arg1 + 0, ptr0, true);
6685
6954
  _debugLog('[iface="wasi:random/random@0.2.3", function="get-random-bytes"][Instruction::Return]', {
@@ -6699,7 +6968,8 @@ let lowered_import_24_metadata = {
6699
6968
 
6700
6969
  function trampoline31(arg0) {
6701
6970
  _debugLog('[iface="wasi:filesystem/preopens@0.2.3", function="get-directories"] [Instruction::CallInterface] (sync, @ enter)');
6702
- const hostProvided = getDirectories._isHostProvided;
6971
+ let hostProvided = false;
6972
+ hostProvided = getDirectories?._isHostProvided;
6703
6973
 
6704
6974
  let parentTask;
6705
6975
  let task;
@@ -6787,7 +7057,8 @@ handleTables[3] = handleTable3;
6787
7057
 
6788
7058
  function trampoline32(arg0) {
6789
7059
  _debugLog('[iface="wasi:cli/terminal-stdin@0.2.3", function="get-terminal-stdin"] [Instruction::CallInterface] (sync, @ enter)');
6790
- const hostProvided = getTerminalStdin._isHostProvided;
7060
+ let hostProvided = false;
7061
+ hostProvided = getTerminalStdin?._isHostProvided;
6791
7062
 
6792
7063
  let parentTask;
6793
7064
  let task;
@@ -6866,7 +7137,8 @@ handleTables[4] = handleTable4;
6866
7137
 
6867
7138
  function trampoline33(arg0) {
6868
7139
  _debugLog('[iface="wasi:cli/terminal-stdout@0.2.3", function="get-terminal-stdout"] [Instruction::CallInterface] (sync, @ enter)');
6869
- const hostProvided = getTerminalStdout._isHostProvided;
7140
+ let hostProvided = false;
7141
+ hostProvided = getTerminalStdout?._isHostProvided;
6870
7142
 
6871
7143
  let parentTask;
6872
7144
  let task;
@@ -6941,7 +7213,8 @@ let lowered_import_27_metadata = {
6941
7213
 
6942
7214
  function trampoline34(arg0) {
6943
7215
  _debugLog('[iface="wasi:cli/terminal-stderr@0.2.3", function="get-terminal-stderr"] [Instruction::CallInterface] (sync, @ enter)');
6944
- const hostProvided = getTerminalStderr._isHostProvided;
7216
+ let hostProvided = false;
7217
+ hostProvided = getTerminalStderr?._isHostProvided;
6945
7218
 
6946
7219
  let parentTask;
6947
7220
  let task;
@@ -7206,7 +7479,7 @@ GlobalComponentAsyncLowers.define({
7206
7479
  isAsync: false,
7207
7480
  paramLiftFns: [],
7208
7481
  metadata: lowered_import_4_metadata,
7209
- resultLowerFns: [_lowerFlatList.bind(null, 0)],
7482
+ resultLowerFns: [_lowerFlatList({ elemLowerFn: _lowerFlatTuple.bind(null, 0), typeIdx: 0 })],
7210
7483
  getCallbackFn: () => null,
7211
7484
  getPostReturnFn: () => null,
7212
7485
  isCancellable: false,
@@ -7227,7 +7500,7 @@ GlobalComponentAsyncLowers.define({
7227
7500
  trampolineIdx: 12,
7228
7501
  componentIdx: 0,
7229
7502
  isAsync: false,
7230
- paramLiftFns: [_liftFlatOwn.bind(null, 6)],
7503
+ paramLiftFns: [_liftFlatBorrow.bind(null, 6)],
7231
7504
  metadata: lowered_import_5_metadata,
7232
7505
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatEnum.bind(null, 0), align32: 1 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7233
7506
  getCallbackFn: () => null,
@@ -7250,7 +7523,7 @@ GlobalComponentAsyncLowers.define({
7250
7523
  trampolineIdx: 13,
7251
7524
  componentIdx: 0,
7252
7525
  isAsync: false,
7253
- paramLiftFns: [_liftFlatOwn.bind(null, 6)],
7526
+ paramLiftFns: [_liftFlatBorrow.bind(null, 6)],
7254
7527
  metadata: lowered_import_6_metadata,
7255
7528
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatRecord([{ field: 'lower', lowerFn: _lowerFlatU64, align32: 8 },{ field: 'upper', lowerFn: _lowerFlatU64, align32: 8 },]), align32: 8 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7256
7529
  getCallbackFn: () => null,
@@ -7273,7 +7546,7 @@ GlobalComponentAsyncLowers.define({
7273
7546
  trampolineIdx: 14,
7274
7547
  componentIdx: 0,
7275
7548
  isAsync: false,
7276
- paramLiftFns: [_liftFlatOwn.bind(null, 0)],
7549
+ paramLiftFns: [_liftFlatBorrow.bind(null, 0)],
7277
7550
  metadata: lowered_import_7_metadata,
7278
7551
  resultLowerFns: [_lowerFlatOption.bind(null, 4)],
7279
7552
  getCallbackFn: () => null,
@@ -7296,7 +7569,7 @@ GlobalComponentAsyncLowers.define({
7296
7569
  trampolineIdx: 15,
7297
7570
  componentIdx: 0,
7298
7571
  isAsync: false,
7299
- paramLiftFns: [_liftFlatOwn.bind(null, 6),_liftFlatFlags.bind(null, 0),_liftFlatStringUTF8],
7572
+ paramLiftFns: [_liftFlatBorrow.bind(null, 6),_liftFlatFlags.bind(null, 0),_liftFlatStringUTF8],
7300
7573
  metadata: lowered_import_8_metadata,
7301
7574
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatRecord([{ field: 'lower', lowerFn: _lowerFlatU64, align32: 8 },{ field: 'upper', lowerFn: _lowerFlatU64, align32: 8 },]), align32: 8 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7302
7575
  getCallbackFn: () => null,
@@ -7319,7 +7592,7 @@ GlobalComponentAsyncLowers.define({
7319
7592
  trampolineIdx: 16,
7320
7593
  componentIdx: 0,
7321
7594
  isAsync: false,
7322
- paramLiftFns: [_liftFlatOwn.bind(null, 6),_liftFlatU64],
7595
+ paramLiftFns: [_liftFlatBorrow.bind(null, 6),_liftFlatU64],
7323
7596
  metadata: lowered_import_9_metadata,
7324
7597
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatOwn.bind(null, 1), align32: 4 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7325
7598
  getCallbackFn: () => null,
@@ -7342,7 +7615,7 @@ GlobalComponentAsyncLowers.define({
7342
7615
  trampolineIdx: 17,
7343
7616
  componentIdx: 0,
7344
7617
  isAsync: false,
7345
- paramLiftFns: [_liftFlatOwn.bind(null, 6),_liftFlatU64],
7618
+ paramLiftFns: [_liftFlatBorrow.bind(null, 6),_liftFlatU64],
7346
7619
  metadata: lowered_import_10_metadata,
7347
7620
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatOwn.bind(null, 2), align32: 4 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7348
7621
  getCallbackFn: () => null,
@@ -7365,7 +7638,7 @@ GlobalComponentAsyncLowers.define({
7365
7638
  trampolineIdx: 18,
7366
7639
  componentIdx: 0,
7367
7640
  isAsync: false,
7368
- paramLiftFns: [_liftFlatOwn.bind(null, 6)],
7641
+ paramLiftFns: [_liftFlatBorrow.bind(null, 6)],
7369
7642
  metadata: lowered_import_11_metadata,
7370
7643
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatOwn.bind(null, 2), align32: 4 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7371
7644
  getCallbackFn: () => null,
@@ -7388,7 +7661,7 @@ GlobalComponentAsyncLowers.define({
7388
7661
  trampolineIdx: 19,
7389
7662
  componentIdx: 0,
7390
7663
  isAsync: false,
7391
- paramLiftFns: [_liftFlatOwn.bind(null, 6)],
7664
+ paramLiftFns: [_liftFlatBorrow.bind(null, 6)],
7392
7665
  metadata: lowered_import_12_metadata,
7393
7666
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatOwn.bind(null, 5), align32: 4 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7394
7667
  getCallbackFn: () => null,
@@ -7411,7 +7684,7 @@ GlobalComponentAsyncLowers.define({
7411
7684
  trampolineIdx: 20,
7412
7685
  componentIdx: 0,
7413
7686
  isAsync: false,
7414
- paramLiftFns: [_liftFlatOwn.bind(null, 6)],
7687
+ paramLiftFns: [_liftFlatBorrow.bind(null, 6)],
7415
7688
  metadata: lowered_import_13_metadata,
7416
7689
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatRecord([{ field: 'type', lowerFn: _lowerFlatEnum.bind(null, 0), align32: 8 },{ field: 'link-count', lowerFn: _lowerFlatU64, align32: 8 },{ field: 'size', lowerFn: _lowerFlatU64, align32: 8 },{ field: 'data-access-timestamp', lowerFn: _lowerFlatOption.bind(null, 2), align32: 8 },{ field: 'data-modification-timestamp', lowerFn: _lowerFlatOption.bind(null, 2), align32: 8 },{ field: 'status-change-timestamp', lowerFn: _lowerFlatOption.bind(null, 2), align32: 8 },]), align32: 8 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7417
7690
  getCallbackFn: () => null,
@@ -7434,7 +7707,7 @@ GlobalComponentAsyncLowers.define({
7434
7707
  trampolineIdx: 21,
7435
7708
  componentIdx: 0,
7436
7709
  isAsync: false,
7437
- paramLiftFns: [_liftFlatOwn.bind(null, 6),_liftFlatFlags.bind(null, 0),_liftFlatStringUTF8],
7710
+ paramLiftFns: [_liftFlatBorrow.bind(null, 6),_liftFlatFlags.bind(null, 0),_liftFlatStringUTF8],
7438
7711
  metadata: lowered_import_14_metadata,
7439
7712
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatRecord([{ field: 'type', lowerFn: _lowerFlatEnum.bind(null, 0), align32: 8 },{ field: 'link-count', lowerFn: _lowerFlatU64, align32: 8 },{ field: 'size', lowerFn: _lowerFlatU64, align32: 8 },{ field: 'data-access-timestamp', lowerFn: _lowerFlatOption.bind(null, 2), align32: 8 },{ field: 'data-modification-timestamp', lowerFn: _lowerFlatOption.bind(null, 2), align32: 8 },{ field: 'status-change-timestamp', lowerFn: _lowerFlatOption.bind(null, 2), align32: 8 },]), align32: 8 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7440
7713
  getCallbackFn: () => null,
@@ -7457,7 +7730,7 @@ GlobalComponentAsyncLowers.define({
7457
7730
  trampolineIdx: 22,
7458
7731
  componentIdx: 0,
7459
7732
  isAsync: false,
7460
- paramLiftFns: [_liftFlatOwn.bind(null, 6),_liftFlatFlags.bind(null, 0),_liftFlatStringUTF8,_liftFlatFlags.bind(null, 1),_liftFlatFlags.bind(null, 2)],
7733
+ paramLiftFns: [_liftFlatBorrow.bind(null, 6),_liftFlatFlags.bind(null, 0),_liftFlatStringUTF8,_liftFlatFlags.bind(null, 1),_liftFlatFlags.bind(null, 2)],
7461
7734
  metadata: lowered_import_15_metadata,
7462
7735
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatOwn.bind(null, 6), align32: 4 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7463
7736
  getCallbackFn: () => null,
@@ -7480,7 +7753,7 @@ GlobalComponentAsyncLowers.define({
7480
7753
  trampolineIdx: 23,
7481
7754
  componentIdx: 0,
7482
7755
  isAsync: false,
7483
- paramLiftFns: [_liftFlatOwn.bind(null, 5)],
7756
+ paramLiftFns: [_liftFlatBorrow.bind(null, 5)],
7484
7757
  metadata: lowered_import_16_metadata,
7485
7758
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatOption.bind(null, 3), align32: 4 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatEnum.bind(null, 1), align32: 1 },])],
7486
7759
  getCallbackFn: () => null,
@@ -7503,9 +7776,9 @@ GlobalComponentAsyncLowers.define({
7503
7776
  trampolineIdx: 24,
7504
7777
  componentIdx: 0,
7505
7778
  isAsync: false,
7506
- paramLiftFns: [_liftFlatOwn.bind(null, 1),_liftFlatU64],
7779
+ paramLiftFns: [_liftFlatBorrow.bind(null, 1),_liftFlatU64],
7507
7780
  metadata: lowered_import_17_metadata,
7508
- resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatList.bind(null, 1), align32: 4 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatVariant({ discriminantSizeBytes: 1, lowerMetas: [{ discriminant: 0, tag: 'last-operation-failed', lowerFn: _lowerFlatOwn.bind(null, 0), align32: 4, },{ discriminant: 1, tag: 'closed', lowerFn: null, align32: null, },] }), align32: 4 },])],
7781
+ resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatList({ elemLowerFn: _lowerFlatU8, typeIdx: 1 }), align32: 4 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatVariant({ discriminantSizeBytes: 1, lowerMetas: [{ discriminant: 0, tag: 'last-operation-failed', lowerFn: _lowerFlatOwn.bind(null, 0), align32: 4, },{ discriminant: 1, tag: 'closed', lowerFn: null, align32: null, },] }), align32: 4 },])],
7509
7782
  getCallbackFn: () => null,
7510
7783
  getPostReturnFn: () => null,
7511
7784
  isCancellable: false,
@@ -7526,9 +7799,9 @@ GlobalComponentAsyncLowers.define({
7526
7799
  trampolineIdx: 25,
7527
7800
  componentIdx: 0,
7528
7801
  isAsync: false,
7529
- paramLiftFns: [_liftFlatOwn.bind(null, 1),_liftFlatU64],
7802
+ paramLiftFns: [_liftFlatBorrow.bind(null, 1),_liftFlatU64],
7530
7803
  metadata: lowered_import_18_metadata,
7531
- resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatList.bind(null, 1), align32: 4 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatVariant({ discriminantSizeBytes: 1, lowerMetas: [{ discriminant: 0, tag: 'last-operation-failed', lowerFn: _lowerFlatOwn.bind(null, 0), align32: 4, },{ discriminant: 1, tag: 'closed', lowerFn: null, align32: null, },] }), align32: 4 },])],
7804
+ resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatList({ elemLowerFn: _lowerFlatU8, typeIdx: 1 }), align32: 4 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatVariant({ discriminantSizeBytes: 1, lowerMetas: [{ discriminant: 0, tag: 'last-operation-failed', lowerFn: _lowerFlatOwn.bind(null, 0), align32: 4, },{ discriminant: 1, tag: 'closed', lowerFn: null, align32: null, },] }), align32: 4 },])],
7532
7805
  getCallbackFn: () => null,
7533
7806
  getPostReturnFn: () => null,
7534
7807
  isCancellable: false,
@@ -7549,7 +7822,7 @@ GlobalComponentAsyncLowers.define({
7549
7822
  trampolineIdx: 26,
7550
7823
  componentIdx: 0,
7551
7824
  isAsync: false,
7552
- paramLiftFns: [_liftFlatOwn.bind(null, 2)],
7825
+ paramLiftFns: [_liftFlatBorrow.bind(null, 2)],
7553
7826
  metadata: lowered_import_19_metadata,
7554
7827
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: _lowerFlatU64, align32: 8 },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatVariant({ discriminantSizeBytes: 1, lowerMetas: [{ discriminant: 0, tag: 'last-operation-failed', lowerFn: _lowerFlatOwn.bind(null, 0), align32: 4, },{ discriminant: 1, tag: 'closed', lowerFn: null, align32: null, },] }), align32: 4 },])],
7555
7828
  getCallbackFn: () => null,
@@ -7572,7 +7845,7 @@ GlobalComponentAsyncLowers.define({
7572
7845
  trampolineIdx: 27,
7573
7846
  componentIdx: 0,
7574
7847
  isAsync: false,
7575
- paramLiftFns: [_liftFlatOwn.bind(null, 2),_liftFlatList.bind(null, 1)],
7848
+ paramLiftFns: [_liftFlatBorrow.bind(null, 2),_liftFlatList.bind(null, 1)],
7576
7849
  metadata: lowered_import_20_metadata,
7577
7850
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: null, align32: null },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatVariant({ discriminantSizeBytes: 1, lowerMetas: [{ discriminant: 0, tag: 'last-operation-failed', lowerFn: _lowerFlatOwn.bind(null, 0), align32: 4, },{ discriminant: 1, tag: 'closed', lowerFn: null, align32: null, },] }), align32: 4 },])],
7578
7851
  getCallbackFn: () => null,
@@ -7595,7 +7868,7 @@ GlobalComponentAsyncLowers.define({
7595
7868
  trampolineIdx: 28,
7596
7869
  componentIdx: 0,
7597
7870
  isAsync: false,
7598
- paramLiftFns: [_liftFlatOwn.bind(null, 2),_liftFlatList.bind(null, 1)],
7871
+ paramLiftFns: [_liftFlatBorrow.bind(null, 2),_liftFlatList.bind(null, 1)],
7599
7872
  metadata: lowered_import_21_metadata,
7600
7873
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: null, align32: null },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatVariant({ discriminantSizeBytes: 1, lowerMetas: [{ discriminant: 0, tag: 'last-operation-failed', lowerFn: _lowerFlatOwn.bind(null, 0), align32: 4, },{ discriminant: 1, tag: 'closed', lowerFn: null, align32: null, },] }), align32: 4 },])],
7601
7874
  getCallbackFn: () => null,
@@ -7618,7 +7891,7 @@ GlobalComponentAsyncLowers.define({
7618
7891
  trampolineIdx: 29,
7619
7892
  componentIdx: 0,
7620
7893
  isAsync: false,
7621
- paramLiftFns: [_liftFlatOwn.bind(null, 2)],
7894
+ paramLiftFns: [_liftFlatBorrow.bind(null, 2)],
7622
7895
  metadata: lowered_import_22_metadata,
7623
7896
  resultLowerFns: [_lowerFlatResult([{ discriminant: 0, tag: 'ok', lowerFn: null, align32: null },{ discriminant: 1, tag: 'error', lowerFn: _lowerFlatVariant({ discriminantSizeBytes: 1, lowerMetas: [{ discriminant: 0, tag: 'last-operation-failed', lowerFn: _lowerFlatOwn.bind(null, 0), align32: 4, },{ discriminant: 1, tag: 'closed', lowerFn: null, align32: null, },] }), align32: 4 },])],
7624
7897
  getCallbackFn: () => null,
@@ -7643,7 +7916,7 @@ GlobalComponentAsyncLowers.define({
7643
7916
  isAsync: false,
7644
7917
  paramLiftFns: [_liftFlatU64],
7645
7918
  metadata: lowered_import_23_metadata,
7646
- resultLowerFns: [_lowerFlatList.bind(null, 1)],
7919
+ resultLowerFns: [_lowerFlatList({ elemLowerFn: _lowerFlatU8, typeIdx: 1 })],
7647
7920
  getCallbackFn: () => null,
7648
7921
  getPostReturnFn: () => null,
7649
7922
  isCancellable: false,
@@ -7666,7 +7939,7 @@ GlobalComponentAsyncLowers.define({
7666
7939
  isAsync: false,
7667
7940
  paramLiftFns: [],
7668
7941
  metadata: lowered_import_24_metadata,
7669
- resultLowerFns: [_lowerFlatList.bind(null, 2)],
7942
+ resultLowerFns: [_lowerFlatList({ elemLowerFn: _lowerFlatTuple.bind(null, 29), typeIdx: 2 })],
7670
7943
  getCallbackFn: () => null,
7671
7944
  getPostReturnFn: () => null,
7672
7945
  isCancellable: false,
@@ -7754,8 +8027,24 @@ function generate(arg0, arg1) {
7754
8027
  var val1 = arg0;
7755
8028
  var len1 = val1.byteLength;
7756
8029
  var ptr1 = realloc1(0, 0, 1, len1 * 1);
7757
- var src1 = new Uint8Array(val1.buffer || val1, val1.byteOffset, len1 * 1);
7758
- (new Uint8Array(memory0.buffer, ptr1, len1 * 1)).set(src1);
8030
+
8031
+ let valData1;
8032
+ const valLenBytes1 = len1 * 1;
8033
+ if (Array.isArray(val1)) {
8034
+ // Regular array likely containing numbers, write values to memory
8035
+ let offset = 0;
8036
+ const dv1 = new DataView(memory0.buffer);
8037
+ for (const v of val1) {
8038
+ dv1.setUint8(ptr1+ offset, v, true);
8039
+ offset += 1;
8040
+ }
8041
+ } else {
8042
+ // TypedArray / ArrayBuffer-like, direct copy
8043
+ valData1 = new Uint8Array(val1.buffer || val1, val1.byteOffset, valLenBytes1);
8044
+ const out1 = new Uint8Array(memory0.buffer, ptr1,valLenBytes1);
8045
+ out1.set(valData1);
8046
+ }
8047
+
7759
8048
  dataView(memory0).setUint32(ptr0 + 4, len1, true);
7760
8049
  dataView(memory0).setUint32(ptr0 + 0, ptr1, true);
7761
8050
  var {name: v2_0, noTypescript: v2_1, instantiation: v2_2, importBindings: v2_3, map: v2_4, compat: v2_5, noNodejsCompat: v2_6, base64Cutoff: v2_7, tlaCompat: v2_8, validLiftingOptimization: v2_9, tracing: v2_10, noNamespacedExports: v2_11, guest: v2_12, multiMemory: v2_13, asyncMode: v2_14 } = arg1;
@@ -8127,8 +8416,24 @@ function generateTypes(arg0, arg1) {
8127
8416
  var val4 = e;
8128
8417
  var len4 = val4.byteLength;
8129
8418
  var ptr4 = realloc1(0, 0, 1, len4 * 1);
8130
- var src4 = new Uint8Array(val4.buffer || val4, val4.byteOffset, len4 * 1);
8131
- (new Uint8Array(memory0.buffer, ptr4, len4 * 1)).set(src4);
8419
+
8420
+ let valData4;
8421
+ const valLenBytes4 = len4 * 1;
8422
+ if (Array.isArray(val4)) {
8423
+ // Regular array likely containing numbers, write values to memory
8424
+ let offset = 0;
8425
+ const dv4 = new DataView(memory0.buffer);
8426
+ for (const v of val4) {
8427
+ dv4.setUint8(ptr4+ offset, v, true);
8428
+ offset += 1;
8429
+ }
8430
+ } else {
8431
+ // TypedArray / ArrayBuffer-like, direct copy
8432
+ valData4 = new Uint8Array(val4.buffer || val4, val4.byteOffset, valLenBytes4);
8433
+ const out4 = new Uint8Array(memory0.buffer, ptr4,valLenBytes4);
8434
+ out4.set(valData4);
8435
+ }
8436
+
8132
8437
  dataView(memory0).setUint32(ptr0 + 16, len4, true);
8133
8438
  dataView(memory0).setUint32(ptr0 + 12, ptr4, true);
8134
8439
  break;