@napi-rs/cli 3.9.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/dist/cli.js +10359 -8729
- package/dist/index.cjs +10381 -8751
- package/dist/index.d.cts +83 -16
- package/dist/index.d.ts +83 -16
- package/dist/index.js +10359 -8729
- package/docs/wasi.md +318 -4
- package/package.json +5 -6
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +4003 -53
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/build-regressions.spec.ts +210 -3
- package/src/api/__tests__/build.spec.ts +2333 -3
- package/src/api/__tests__/create-npm-dirs.spec.ts +105 -3
- package/src/api/__tests__/pre-publish.spec.ts +153 -1
- package/src/api/__tests__/templates.spec.ts +1309 -1
- package/src/api/build.ts +756 -30
- package/src/api/create-npm-dirs.ts +23 -10
- package/src/api/new.ts +13 -18
- package/src/api/pre-publish.ts +34 -11
- package/src/api/rename.ts +10 -21
- package/src/api/templates/binding-target.ts +176 -0
- package/src/api/templates/index.ts +1 -0
- package/src/api/templates/js-binding.ts +54 -10
- package/src/api/templates/load-wasi-template.ts +661 -58
- package/src/api/templates/wasi-worker-template.ts +36 -31
- package/src/commands/build.ts +1 -1
- package/src/utils/__tests__/__snapshots__/typegen.spec.ts.md +18 -28
- package/src/utils/__tests__/__snapshots__/typegen.spec.ts.snap +0 -0
- package/src/utils/__tests__/misc.spec.ts +4 -0
- package/src/utils/__tests__/reconciliation.spec.ts +676 -0
- package/src/utils/__tests__/serialize.spec.ts +55 -0
- package/src/utils/__tests__/target.spec.ts +221 -0
- package/src/utils/__tests__/typegen.spec.ts +115 -0
- package/src/utils/config.ts +50 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +351 -79
- package/src/utils/serialize.ts +47 -0
- package/src/utils/target.ts +150 -1
- package/src/utils/typegen.ts +608 -42
|
@@ -1,24 +1,150 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BINDING_TARGET_STAMP_HELPER,
|
|
3
|
+
NAPI_BINDING_TARGET_EXPORT,
|
|
4
|
+
NAPI_BINDING_TARGET_STAMP_FN,
|
|
5
|
+
} from './binding-target.js'
|
|
6
|
+
|
|
1
7
|
const WASI_DISPOSE_SYMBOL = 'napi.rs.wasi.dispose'
|
|
2
8
|
const WASI_ROLLBACK_REGISTRY_SYMBOL = 'napi.rs.wasi.rollback.registry.v1'
|
|
3
9
|
|
|
4
|
-
|
|
10
|
+
/**
|
|
11
|
+
* `Context.destroy()` disables JavaScript calls *before* it runs cleanup hooks
|
|
12
|
+
* (`setStopping` -> `setCanCallIntoJs(false)` -> `runCleanup`), and the
|
|
13
|
+
* threadsafe function's cleanup hook then drains its queue with a null env and
|
|
14
|
+
* discards it. So `napi_prepare_wasm_env_cleanup` has to run while the
|
|
15
|
+
* environment is still live — before `destroy()`, never from a hook inside it.
|
|
16
|
+
*
|
|
17
|
+
* The loaders already order their own teardown that way. A `destroy()` called
|
|
18
|
+
* by anyone else — an embedder or test harness holding the context, emnapi's
|
|
19
|
+
* own `beforeExit` auto-destroy on a host where `suppressDestroy()` is absent —
|
|
20
|
+
* would skip the barrier and discard exactly the settlements it exists to
|
|
21
|
+
* cancel and deliver. Own the ordering on the object rather than on each call
|
|
22
|
+
* site: shadow `destroy` once at creation, so every caller gets the barrier.
|
|
23
|
+
*
|
|
24
|
+
* The barrier is reentrant-hostile, so the wrapper has to be reentrancy-aware.
|
|
25
|
+
* `napi_prepare_wasm_env_cleanup` settles the promises it cancels synchronously,
|
|
26
|
+
* under a non-reentrant lifecycle mutex on the Rust side; a V8 promise hook
|
|
27
|
+
* (`promiseHooks.onSettled`, or the `async_hooks` hook `AsyncLocalStorage`
|
|
28
|
+
* installs) that calls `destroy()` therefore re-enters this wrapper from inside
|
|
29
|
+
* the barrier, and calling the barrier again aborts the whole wasm instance.
|
|
30
|
+
* While a prepare is in flight the nested `destroy()` is a no-op rather than a
|
|
31
|
+
* deferred one: the frame that started the barrier destroys the moment it
|
|
32
|
+
* returns, still synchronously, and `Context.destroy()` is typed `void`, so
|
|
33
|
+
* answering `undefined` loses nothing a caller could have observed. Letting the
|
|
34
|
+
* nested call through instead would tear the environment down mid-barrier and
|
|
35
|
+
* strand every settlement the barrier had not reached yet.
|
|
36
|
+
*
|
|
37
|
+
* Defensive, not strict: a context whose `destroy` cannot be read or redefined
|
|
38
|
+
* is returned unchanged rather than failing the load. A barrier that throws
|
|
39
|
+
* still propagates, exactly as it does from `__destroyEmnapiContext`.
|
|
40
|
+
*
|
|
41
|
+
* This is NOT a replacement for `dispose()`: only the disposal chain yields
|
|
42
|
+
* event-loop turns until `napi_wasm_env_cleanup_pending` reads zero, so a
|
|
43
|
+
* direct `destroy()` still cannot wait for a settlement queued by another
|
|
44
|
+
* thread. It delivers everything the barrier settles on this thread.
|
|
45
|
+
*/
|
|
46
|
+
const emnapiContextDestroyWrapper = `
|
|
47
|
+
function __wrapEmnapiContextDestroyForSettlement(
|
|
48
|
+
context,
|
|
49
|
+
prepareEnvCleanup,
|
|
50
|
+
isPreparingEnvCleanup,
|
|
51
|
+
) {
|
|
52
|
+
let destroy
|
|
53
|
+
try {
|
|
54
|
+
destroy = context.destroy
|
|
55
|
+
} catch {
|
|
56
|
+
return context
|
|
57
|
+
}
|
|
58
|
+
if (typeof destroy !== 'function') {
|
|
59
|
+
return context
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
Object.defineProperty(context, 'destroy', {
|
|
63
|
+
configurable: true,
|
|
64
|
+
enumerable: false,
|
|
65
|
+
writable: true,
|
|
66
|
+
value: function () {
|
|
67
|
+
// Reentered from a promise hook that fired inside the barrier: the
|
|
68
|
+
// frame running it destroys as soon as it returns.
|
|
69
|
+
if (isPreparingEnvCleanup?.()) {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
prepareEnvCleanup?.()
|
|
73
|
+
return Reflect.apply(destroy, this, arguments)
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
} catch {}
|
|
77
|
+
return context
|
|
78
|
+
}
|
|
79
|
+
`
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Host teardown must run while the environment can still accept N-API calls,
|
|
83
|
+
* and after the settlement drain: the drain is what lets the barrier's queued
|
|
84
|
+
* promise settlements reach JavaScript, and the task host is what publishes the
|
|
85
|
+
* CurrentThread turns they may still need. `__destroyEmnapiContext` is the
|
|
86
|
+
* single funnel every teardown path reaches — `dispose()`, the initialization
|
|
87
|
+
* rollback, and the CJS 'exit' handler — and it is reached only after
|
|
88
|
+
* `__startWasiDisposal` / the rollback have already prepared and drained, so
|
|
89
|
+
* one call there covers all three.
|
|
90
|
+
*/
|
|
91
|
+
const createEmnapiContextLifecycle = (asyncRuntime: boolean) => {
|
|
92
|
+
const currentThreadHosts = asyncRuntime
|
|
93
|
+
? `
|
|
94
|
+
let __currentThreadHostsDisposer
|
|
95
|
+
|
|
96
|
+
function __reportCurrentThreadHostDisposalError(error) {
|
|
97
|
+
try {
|
|
98
|
+
const consoleHost = globalThis.console
|
|
99
|
+
if (consoleHost && typeof consoleHost.error === 'function') {
|
|
100
|
+
consoleHost.error(error)
|
|
101
|
+
}
|
|
102
|
+
} catch {}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Unregister the CurrentThread task and timer hosts this loader installed.
|
|
107
|
+
* Idempotent, and never throws: an unregister failure must not abort
|
|
108
|
+
* \`Context.destroy()\`, which would retain the whole environment over a
|
|
109
|
+
* bookkeeping error. The failure is reported instead.
|
|
110
|
+
*/
|
|
111
|
+
function __disposeCurrentThreadHosts() {
|
|
112
|
+
const dispose = __currentThreadHostsDisposer
|
|
113
|
+
if (dispose === undefined) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
__currentThreadHostsDisposer = undefined
|
|
117
|
+
try {
|
|
118
|
+
dispose()
|
|
119
|
+
} catch (error) {
|
|
120
|
+
__reportCurrentThreadHostDisposalError(error)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
`
|
|
124
|
+
: ''
|
|
125
|
+
const disposeCurrentThreadHosts = asyncRuntime
|
|
126
|
+
? ' __disposeCurrentThreadHosts()\n'
|
|
127
|
+
: ''
|
|
128
|
+
|
|
129
|
+
return `
|
|
5
130
|
const __wasiDisposeSymbol = Symbol.for('${WASI_DISPOSE_SYMBOL}')
|
|
6
131
|
const __wasiWorkers = new Set()
|
|
7
132
|
let __napiInstance
|
|
8
133
|
let __emnapiContextDestroyed = false
|
|
9
134
|
let __emnapiContextDestroyPromise
|
|
10
135
|
let __emnapiWasmEnvCleanupPrepared = false
|
|
136
|
+
let __emnapiWasmEnvCleanupPreparing = false
|
|
11
137
|
let __emnapiWasmEnvCleanupRan = false
|
|
12
138
|
let __emnapiWasmEnvCleanupDrained = false
|
|
13
139
|
let __emnapiWasmEnvCleanupDrainPromise
|
|
14
140
|
let __wasiDisposed = false
|
|
15
141
|
let __wasiDisposePromise
|
|
16
|
-
let __completeWasiDisposal = function() {}
|
|
142
|
+
let __completeWasiDisposal = function () {}
|
|
17
143
|
// Overridden by loader flavors that have a last-resort reclaim for a rollback
|
|
18
144
|
// that stopped short of destroying the context. See
|
|
19
145
|
// \`__rollbackWasiInitialization\`.
|
|
20
|
-
let __retainWasiRollbackForRetry = function() {}
|
|
21
|
-
|
|
146
|
+
let __retainWasiRollbackForRetry = function () {}
|
|
147
|
+
${currentThreadHosts}
|
|
22
148
|
function __isThenable(value) {
|
|
23
149
|
return (
|
|
24
150
|
value !== null &&
|
|
@@ -80,14 +206,26 @@ function __attachCleanupErrors(error, cleanupErrors) {
|
|
|
80
206
|
} catch {}
|
|
81
207
|
return aggregate
|
|
82
208
|
}
|
|
209
|
+
${emnapiContextDestroyWrapper}
|
|
210
|
+
function __isPreparingWasmEnvCleanup() {
|
|
211
|
+
return __emnapiWasmEnvCleanupPreparing
|
|
212
|
+
}
|
|
83
213
|
|
|
84
214
|
function __prepareWasmEnvCleanup() {
|
|
85
|
-
if (__emnapiWasmEnvCleanupPrepared) {
|
|
215
|
+
if (__emnapiWasmEnvCleanupPrepared || __emnapiWasmEnvCleanupPreparing) {
|
|
86
216
|
return
|
|
87
217
|
}
|
|
88
218
|
const prepare = __napiInstance?.exports?.napi_prepare_wasm_env_cleanup
|
|
89
219
|
if (typeof prepare === 'function') {
|
|
90
|
-
|
|
220
|
+
// The addon settles the promises it cancels synchronously, under a
|
|
221
|
+
// non-reentrant lifecycle mutex: anything a promise hook calls from in
|
|
222
|
+
// here must not reach this export again.
|
|
223
|
+
__emnapiWasmEnvCleanupPreparing = true
|
|
224
|
+
try {
|
|
225
|
+
prepare()
|
|
226
|
+
} finally {
|
|
227
|
+
__emnapiWasmEnvCleanupPreparing = false
|
|
228
|
+
}
|
|
91
229
|
__emnapiWasmEnvCleanupRan = true
|
|
92
230
|
}
|
|
93
231
|
__emnapiWasmEnvCleanupPrepared = true
|
|
@@ -259,6 +397,7 @@ function __destroyEmnapiContext() {
|
|
|
259
397
|
return __emnapiContextDestroyPromise
|
|
260
398
|
}
|
|
261
399
|
|
|
400
|
+
${disposeCurrentThreadHosts}\
|
|
262
401
|
__prepareWasmEnvCleanup()
|
|
263
402
|
const result = __emnapiContext.destroy()
|
|
264
403
|
if (!__isThenable(result)) {
|
|
@@ -513,6 +652,7 @@ function __rollbackWasiInitialization() {
|
|
|
513
652
|
return __destroyContextForWasiRollback(cleanupErrors)
|
|
514
653
|
}
|
|
515
654
|
`
|
|
655
|
+
}
|
|
516
656
|
|
|
517
657
|
export const createWasiBrowserBinding = (
|
|
518
658
|
wasiFilename: string,
|
|
@@ -523,11 +663,24 @@ export const createWasiBrowserBinding = (
|
|
|
523
663
|
buffer = false,
|
|
524
664
|
errorEvent = false,
|
|
525
665
|
threads = true,
|
|
666
|
+
// `platformArchABI` of the flavor this loader belongs to. Defaults from
|
|
667
|
+
// `threads` so callers that predate the parameter keep their identity.
|
|
668
|
+
platformArchABI = threads ? 'wasm32-wasi' : 'wasm32-wasip1',
|
|
669
|
+
asyncRuntime = false,
|
|
526
670
|
) => {
|
|
527
671
|
// Threaded builds always get a pre-created worker pool (see
|
|
528
672
|
// `reuseWorkerOption` below), and pool pre-creation is asynchronous, so
|
|
529
673
|
// they always initialize asynchronously.
|
|
530
674
|
const effectiveAsyncInit = asyncInit || threads
|
|
675
|
+
const asyncRuntimeImport = asyncRuntime
|
|
676
|
+
? `import { installCurrentThreadHosts as __installCurrentThreadHosts } from '@napi-rs/async-runtime'\n`
|
|
677
|
+
: ''
|
|
678
|
+
const installAsyncRuntimeHosts = asyncRuntime
|
|
679
|
+
? ` __currentThreadHostsDisposer = __installCurrentThreadHosts(
|
|
680
|
+
__napiModule.exports,
|
|
681
|
+
)
|
|
682
|
+
`
|
|
683
|
+
: ''
|
|
531
684
|
const fsImport = fs
|
|
532
685
|
? buffer
|
|
533
686
|
? `import { memfs, Buffer } from '@napi-rs/wasm-runtime/fs'`
|
|
@@ -652,8 +805,11 @@ ${workerRuntimeImport}\
|
|
|
652
805
|
WASI as __WASI,
|
|
653
806
|
} from '@napi-rs/wasm-runtime'
|
|
654
807
|
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
|
|
808
|
+
${asyncRuntimeImport}\
|
|
655
809
|
${fsImport}
|
|
656
810
|
${bufferImport}
|
|
811
|
+
export const __napiBindingTarget = '${platformArchABI}'
|
|
812
|
+
${BINDING_TARGET_STAMP_HELPER}
|
|
657
813
|
${wasiCreation}
|
|
658
814
|
|
|
659
815
|
const __wasmUrl = new URL('./${wasiFilename}.wasm', import.meta.url).href
|
|
@@ -677,12 +833,16 @@ ${threads ? ' shared: true,\n' : ''}\
|
|
|
677
833
|
})
|
|
678
834
|
${workerPoolSizeBinding}\
|
|
679
835
|
let __emnapiContext
|
|
680
|
-
${
|
|
836
|
+
${createEmnapiContextLifecycle(asyncRuntime)}
|
|
681
837
|
let __wasiModule
|
|
682
838
|
let __napiModule
|
|
683
839
|
|
|
684
840
|
try {
|
|
685
|
-
__emnapiContext =
|
|
841
|
+
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
|
842
|
+
__emnapiCreateContext({ autoDestroy: false }),
|
|
843
|
+
__prepareWasmEnvCleanup,
|
|
844
|
+
__isPreparingWasmEnvCleanup,
|
|
845
|
+
)
|
|
686
846
|
__emnapiContext.suppressDestroy()
|
|
687
847
|
${emnapiInjectBuffer}
|
|
688
848
|
;({
|
|
@@ -715,6 +875,13 @@ ${workerOption}\
|
|
|
715
875
|
},
|
|
716
876
|
}))
|
|
717
877
|
__publishWasiDispose(__napiModule.exports)
|
|
878
|
+
${installAsyncRuntimeHosts}\
|
|
879
|
+
// The default export hands out this object; a named module export does not
|
|
880
|
+
// travel with it, so carry the marker on the binding itself too. After the
|
|
881
|
+
// host install, which hands the same object to addon-provided registration
|
|
882
|
+
// functions that may put anything on it, and inside this \`try\`, so a claimed
|
|
883
|
+
// name fails the load through the rollback below rather than past it.
|
|
884
|
+
${NAPI_BINDING_TARGET_STAMP_FN}(__napiModule.exports, __napiBindingTarget)
|
|
718
885
|
} catch (error) {
|
|
719
886
|
const cleanupErrors = await __rollbackWasiInitialization()
|
|
720
887
|
throw __attachCleanupErrors(error, cleanupErrors)
|
|
@@ -722,19 +889,232 @@ ${workerOption}\
|
|
|
722
889
|
`
|
|
723
890
|
}
|
|
724
891
|
|
|
892
|
+
/**
|
|
893
|
+
* Module-scope prelude of the deferred loader: the compiled-in memory
|
|
894
|
+
* descriptor, the per-module instance counters, and the resolver that turns a
|
|
895
|
+
* `createInstance()` options bag into the one `WebAssembly.Memory` that
|
|
896
|
+
* instance runs on.
|
|
897
|
+
*
|
|
898
|
+
* Nothing here allocates — workerd bans allocation in global scope, so the
|
|
899
|
+
* Memory itself is created inside `__createInstance`.
|
|
900
|
+
*/
|
|
901
|
+
const DEFERRED_MEMORY_PREAMBLE = (
|
|
902
|
+
initialMemory: number,
|
|
903
|
+
maximumMemory: number,
|
|
904
|
+
) => `
|
|
905
|
+
export const WASM_MEMORY = Object.freeze({
|
|
906
|
+
initialPages: ${initialMemory},
|
|
907
|
+
maximumPages: ${maximumMemory},
|
|
908
|
+
pageBytes: 65536,
|
|
909
|
+
initialBytes: ${initialMemory} * 65536,
|
|
910
|
+
maximumBytes: ${maximumMemory} * 65536,
|
|
911
|
+
})
|
|
912
|
+
|
|
913
|
+
let __createdInstances = 0
|
|
914
|
+
let __liveInstances = 0
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Counters for instances created by THIS module evaluation, not process-wide:
|
|
918
|
+
* a second bundled copy of this loader keeps its own. Only successfully
|
|
919
|
+
* created instances are counted, and \`liveInstances\` drops when an instance's
|
|
920
|
+
* \`dispose()\` resolves.
|
|
921
|
+
*
|
|
922
|
+
* \`declaredInitialMemoryBytes\` is declared address space, not a host's
|
|
923
|
+
* committed-memory metric; pair it with host telemetry rather than treating it
|
|
924
|
+
* as a quota.
|
|
925
|
+
*/
|
|
926
|
+
export function getDeferredRuntimeStats() {
|
|
927
|
+
return Object.freeze({
|
|
928
|
+
createdInstances: __createdInstances,
|
|
929
|
+
liveInstances: __liveInstances,
|
|
930
|
+
declaredInitialMemoryBytes: WASM_MEMORY.initialBytes,
|
|
931
|
+
})
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const __arrayBufferByteLengthGetter = Object.getOwnPropertyDescriptor(
|
|
935
|
+
ArrayBuffer.prototype,
|
|
936
|
+
'byteLength',
|
|
937
|
+
).get
|
|
938
|
+
const __memoryBufferGetter = Object.getOwnPropertyDescriptor(
|
|
939
|
+
WebAssembly.Memory.prototype,
|
|
940
|
+
'buffer',
|
|
941
|
+
).get
|
|
942
|
+
// One managed initialization per Memory, success or failure: an attempt that
|
|
943
|
+
// throws may already have written into linear memory, so the bytes are not a
|
|
944
|
+
// clean slate for a second instance. Module-local, like the counters above.
|
|
945
|
+
const __claimedMemories = new WeakSet()
|
|
946
|
+
|
|
947
|
+
function __resolveInstanceMemory(__options) {
|
|
948
|
+
const __provided = __options == null ? undefined : __options.memory
|
|
949
|
+
if (__provided === undefined || __provided === null) {
|
|
950
|
+
// Page counts are handed to the engine unvalidated: it already rejects a
|
|
951
|
+
// negative, over-4GiB or below-maximum value with a precise message, and a
|
|
952
|
+
// second set of bounds here would only drift from it.
|
|
953
|
+
const __allocated = new WebAssembly.Memory({
|
|
954
|
+
initial:
|
|
955
|
+
__options != null && __options.initialMemoryPages !== undefined
|
|
956
|
+
? __options.initialMemoryPages
|
|
957
|
+
: WASM_MEMORY.initialPages,
|
|
958
|
+
maximum:
|
|
959
|
+
__options != null && __options.maximumMemoryPages !== undefined
|
|
960
|
+
? __options.maximumMemoryPages
|
|
961
|
+
: WASM_MEMORY.maximumPages,
|
|
962
|
+
})
|
|
963
|
+
// Claimed like a caller-provided one. The handle publishes it as
|
|
964
|
+
// \`instance.memory\`, so handing it back to \`createInstance()\` is as easy
|
|
965
|
+
// as passing your own twice, and it would put two live instances on one
|
|
966
|
+
// linear memory: each initialization rewrites the emnapi/WASI state the
|
|
967
|
+
// other is still running on.
|
|
968
|
+
__claimedMemories.add(__allocated)
|
|
969
|
+
return __allocated
|
|
970
|
+
}
|
|
971
|
+
if (
|
|
972
|
+
__options.initialMemoryPages !== undefined ||
|
|
973
|
+
__options.maximumMemoryPages !== undefined
|
|
974
|
+
) {
|
|
975
|
+
throw new TypeError(
|
|
976
|
+
'Pass either memory or initialMemoryPages/maximumMemoryPages, not both',
|
|
977
|
+
)
|
|
978
|
+
}
|
|
979
|
+
let __buffer
|
|
980
|
+
try {
|
|
981
|
+
// Brand check: the getter throws for anything that is not a genuine
|
|
982
|
+
// WebAssembly.Memory, including a cross-realm look-alike object.
|
|
983
|
+
__buffer = Reflect.apply(__memoryBufferGetter, __provided, [])
|
|
984
|
+
} catch {
|
|
985
|
+
throw new TypeError('memory must be an unshared WebAssembly.Memory')
|
|
986
|
+
}
|
|
987
|
+
try {
|
|
988
|
+
// Throws for a SharedArrayBuffer. This loader has no threads, and shared
|
|
989
|
+
// growth does not detach: external views handed to the addon would
|
|
990
|
+
// silently outlive the bytes they describe.
|
|
991
|
+
Reflect.apply(__arrayBufferByteLengthGetter, __buffer, [])
|
|
992
|
+
} catch {
|
|
993
|
+
throw new TypeError(
|
|
994
|
+
'The deferred loader requires an unshared WebAssembly.Memory',
|
|
995
|
+
)
|
|
996
|
+
}
|
|
997
|
+
// The intrinsic getters above accept a genuine Memory from ANY realm, but
|
|
998
|
+
// the loader's dependencies do not: \`WASI.setMemory\` in
|
|
999
|
+
// \`@napi-rs/wasm-runtime\` and emnapi identify a Memory with a realm-local
|
|
1000
|
+
// \`instanceof\`. A Memory built in another realm (a \`node:vm\` context, a
|
|
1001
|
+
// same-origin iframe) would pass every check here and only fail deep inside
|
|
1002
|
+
// initialization. Reject it up front, and before the claim below, so the
|
|
1003
|
+
// caller keeps it usable in the realm that made it.
|
|
1004
|
+
if (!(__provided instanceof WebAssembly.Memory)) {
|
|
1005
|
+
throw new TypeError(
|
|
1006
|
+
'memory must be a WebAssembly.Memory created in the same realm as this loader',
|
|
1007
|
+
)
|
|
1008
|
+
}
|
|
1009
|
+
if (__claimedMemories.has(__provided)) {
|
|
1010
|
+
throw new TypeError(
|
|
1011
|
+
'This WebAssembly.Memory has already been used for a deferred initialization attempt and cannot be reused, including after a failed initialization or a disposal',
|
|
1012
|
+
)
|
|
1013
|
+
}
|
|
1014
|
+
// Last step, after every check: a rejected option bag must leave the Memory
|
|
1015
|
+
// unclaimed, or a caller could not fix the call and retry with it.
|
|
1016
|
+
__claimedMemories.add(__provided)
|
|
1017
|
+
return __provided
|
|
1018
|
+
}
|
|
1019
|
+
`
|
|
1020
|
+
|
|
725
1021
|
export const createWasiDeferredBrowserBinding = (
|
|
726
1022
|
wasiFilename: string,
|
|
1023
|
+
// Fed by `napi.wasm.threadlessInitialMemory ?? napi.wasm.initialMemory`.
|
|
727
1024
|
// 64 MiB leaves headroom for JS/runtime state under workerd's 128 MiB
|
|
728
1025
|
// isolate limit. The regular Node/browser loaders retain their historical
|
|
729
1026
|
// 4,000-page default.
|
|
730
1027
|
initialMemory = 1024,
|
|
731
1028
|
maximumMemory = 65536,
|
|
732
1029
|
buffer = false,
|
|
1030
|
+
// Deferred loaders are only emitted for non-threaded flavors, so the
|
|
1031
|
+
// default matches the only flavor `napi build` generates one for.
|
|
1032
|
+
platformArchABI = 'wasm32-wasip1',
|
|
1033
|
+
asyncRuntime = false,
|
|
733
1034
|
) => {
|
|
734
1035
|
const bufferImport = buffer ? `import { Buffer } from 'buffer'` : ''
|
|
735
1036
|
const emnapiInjectBuffer = buffer
|
|
736
1037
|
? ' __emnapiContext.features.Buffer = Buffer\n'
|
|
737
1038
|
: ''
|
|
1039
|
+
// This flavor creates N independent instances per realm, each with its own
|
|
1040
|
+
// emnapi context and its own `napiModule.exports`, so it uses the
|
|
1041
|
+
// per-instance helpers rather than `installCurrentThreadHosts`: those return
|
|
1042
|
+
// exact, idempotent disposers with no realm-global dedup, roll themselves
|
|
1043
|
+
// back on a setup failure, and degrade to a no-op disposer when the realm has
|
|
1044
|
+
// no `setTimeout`/`clearTimeout`.
|
|
1045
|
+
// The `/workerd` subpath, not the barrel: the barrel's `index.cjs` also
|
|
1046
|
+
// requires `current-thread-hosts.cjs`, whose realm-global registry and Node
|
|
1047
|
+
// timer-handle bookkeeping this flavor never executes, and a CJS barrel is
|
|
1048
|
+
// not tree-shakeable out of a worker bundle.
|
|
1049
|
+
const asyncRuntimeImport = asyncRuntime
|
|
1050
|
+
? `import {
|
|
1051
|
+
registerWorkerdCurrentThreadTaskHost as __registerWorkerdCurrentThreadTaskHost,
|
|
1052
|
+
registerWorkerdTimerHost as __registerWorkerdTimerHost,
|
|
1053
|
+
} from '@napi-rs/async-runtime/workerd'
|
|
1054
|
+
`
|
|
1055
|
+
: ''
|
|
1056
|
+
// `__createManagedEmnapiContext` calls `__prepareEnvCleanup?.()` on EVERY
|
|
1057
|
+
// destroy path (dispose(), managed beforeExit, module lifecycle), so a second
|
|
1058
|
+
// hook next to it covers them all with one edit.
|
|
1059
|
+
const managedHostDisposeParam = asyncRuntime ? ' __disposeHosts,\n' : ''
|
|
1060
|
+
const managedHostDisposeCall = asyncRuntime
|
|
1061
|
+
? ` __disposeHosts?.()\n`
|
|
1062
|
+
: ''
|
|
1063
|
+
const instanceHostState = asyncRuntime
|
|
1064
|
+
? ` let __disposeInstanceHosts
|
|
1065
|
+
const __reportInstanceHostDisposalError = (__error) => {
|
|
1066
|
+
try {
|
|
1067
|
+
const __consoleHost = globalThis.console
|
|
1068
|
+
if (__consoleHost && typeof __consoleHost.error === 'function') {
|
|
1069
|
+
__consoleHost.error(__error)
|
|
1070
|
+
}
|
|
1071
|
+
} catch {}
|
|
1072
|
+
}
|
|
1073
|
+
// Runs between the settlement drain and \`Context.destroy()\`; never throws,
|
|
1074
|
+
// for the same reason the eager loaders' disposer does not.
|
|
1075
|
+
const __disposeHostsBeforeDestroy = () => {
|
|
1076
|
+
const __dispose = __disposeInstanceHosts
|
|
1077
|
+
if (__dispose === undefined) {
|
|
1078
|
+
return
|
|
1079
|
+
}
|
|
1080
|
+
__disposeInstanceHosts = undefined
|
|
1081
|
+
__dispose()
|
|
1082
|
+
}
|
|
1083
|
+
`
|
|
1084
|
+
: ''
|
|
1085
|
+
const installInstanceHosts = asyncRuntime
|
|
1086
|
+
? ` const __disposeTaskHost = __registerWorkerdCurrentThreadTaskHost(
|
|
1087
|
+
__napiModule.exports,
|
|
1088
|
+
)
|
|
1089
|
+
try {
|
|
1090
|
+
const __disposeTimerHost = __registerWorkerdTimerHost(
|
|
1091
|
+
__napiModule.exports,
|
|
1092
|
+
)
|
|
1093
|
+
__disposeInstanceHosts = () => {
|
|
1094
|
+
try {
|
|
1095
|
+
__disposeTimerHost()
|
|
1096
|
+
} catch (__error) {
|
|
1097
|
+
__reportInstanceHostDisposalError(__error)
|
|
1098
|
+
}
|
|
1099
|
+
try {
|
|
1100
|
+
__disposeTaskHost()
|
|
1101
|
+
} catch (__error) {
|
|
1102
|
+
__reportInstanceHostDisposalError(__error)
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
} catch (__error) {
|
|
1106
|
+
try {
|
|
1107
|
+
__disposeTaskHost()
|
|
1108
|
+
} catch (__cleanupError) {
|
|
1109
|
+
__attachCleanupError(__error, __cleanupError)
|
|
1110
|
+
}
|
|
1111
|
+
throw __error
|
|
1112
|
+
}
|
|
1113
|
+
`
|
|
1114
|
+
: ''
|
|
1115
|
+
const managedHostDisposeArg = asyncRuntime
|
|
1116
|
+
? ' __disposeHostsBeforeDestroy,\n'
|
|
1117
|
+
: ''
|
|
738
1118
|
return `import {
|
|
739
1119
|
emnapiAsyncWorkPlugin as __emnapiAsyncWorkPlugin,
|
|
740
1120
|
emnapiTSFNPlugin as __emnapiTSFNPlugin,
|
|
@@ -742,7 +1122,11 @@ export const createWasiDeferredBrowserBinding = (
|
|
|
742
1122
|
WASI as __WASI,
|
|
743
1123
|
} from '@napi-rs/wasm-runtime'
|
|
744
1124
|
import { createContext as __emnapiCreateContext } from '@emnapi/runtime'
|
|
1125
|
+
${asyncRuntimeImport}\
|
|
745
1126
|
${bufferImport}
|
|
1127
|
+
${DEFERRED_MEMORY_PREAMBLE(initialMemory, maximumMemory)}
|
|
1128
|
+
export const __napiBindingTarget = '${platformArchABI}'
|
|
1129
|
+
${BINDING_TARGET_STAMP_HELPER}
|
|
746
1130
|
|
|
747
1131
|
/**
|
|
748
1132
|
* Deferred, workerd-safe instantiation: no top-level I/O, no compile-from-bytes.
|
|
@@ -841,7 +1225,7 @@ async function __normalizeModuleForEmnapi(__module) {
|
|
|
841
1225
|
'provide structuredClone or MessageChannel support.',
|
|
842
1226
|
)
|
|
843
1227
|
}
|
|
844
|
-
|
|
1228
|
+
${emnapiContextDestroyWrapper}
|
|
845
1229
|
function __captureEmnapiAutoDestroyListener(__process) {
|
|
846
1230
|
if (
|
|
847
1231
|
!__process ||
|
|
@@ -1229,7 +1613,10 @@ function __registerManagedEmnapiContext(__process, __destroy) {
|
|
|
1229
1613
|
}
|
|
1230
1614
|
}
|
|
1231
1615
|
|
|
1232
|
-
async function __createManagedEmnapiContext(
|
|
1616
|
+
async function __createManagedEmnapiContext(
|
|
1617
|
+
__prepareEnvCleanup,
|
|
1618
|
+
__isPreparingEnvCleanup,
|
|
1619
|
+
${managedHostDisposeParam}) {
|
|
1233
1620
|
const __process =
|
|
1234
1621
|
typeof process === 'object' && process !== null ? process : undefined
|
|
1235
1622
|
const __finishAutoDestroyCapture =
|
|
@@ -1238,7 +1625,11 @@ async function __createManagedEmnapiContext(__prepareEnvCleanup) {
|
|
|
1238
1625
|
let __contextInitializationError
|
|
1239
1626
|
let __contextInitializationFailed = false
|
|
1240
1627
|
try {
|
|
1241
|
-
__emnapiContext =
|
|
1628
|
+
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
|
1629
|
+
__emnapiCreateContext({ autoDestroy: false }),
|
|
1630
|
+
__prepareEnvCleanup,
|
|
1631
|
+
__isPreparingEnvCleanup,
|
|
1632
|
+
)
|
|
1242
1633
|
// emnapi 2.x still registers an unconditional process.once('beforeExit')
|
|
1243
1634
|
// auto-destroy listener on Node hosts, and suppressDestroy() only
|
|
1244
1635
|
// neutralizes its callback without removing it. This loader must stay
|
|
@@ -1288,6 +1679,18 @@ async function __createManagedEmnapiContext(__prepareEnvCleanup) {
|
|
|
1288
1679
|
// Context.destroy() disables JS before cleanup hooks run, so settle
|
|
1289
1680
|
// runtime-owned promises while this environment can still call JS.
|
|
1290
1681
|
__prepareEnvCleanup?.()
|
|
1682
|
+
if (__isPreparingEnvCleanup?.()) {
|
|
1683
|
+
// Reached from inside the barrier, so \`Context.destroy()\` below would
|
|
1684
|
+
// hit the wrapper's in-flight no-op. Recording that as a completed
|
|
1685
|
+
// destroy is what makes the frame that *did* start the barrier skip the
|
|
1686
|
+
// real one afterwards, leaving the context retained with its cleanup
|
|
1687
|
+
// hooks unrun. Refuse instead: nothing is flagged, the context stays
|
|
1688
|
+
// registered for managed beforeExit cleanup, and a later destroy still
|
|
1689
|
+
// works. dispose() coalesces reentrancy before it can get here, so this
|
|
1690
|
+
// is the backstop for any other caller that manages to.
|
|
1691
|
+
throw __createLifecycleReentryError('dispose')
|
|
1692
|
+
}
|
|
1693
|
+
${managedHostDisposeCall}\
|
|
1291
1694
|
__result = __emnapiContext.destroy()
|
|
1292
1695
|
} catch (error) {
|
|
1293
1696
|
__finishDestroyInvocation()
|
|
@@ -1392,6 +1795,7 @@ async function __createManagedEmnapiContext(__prepareEnvCleanup) {
|
|
|
1392
1795
|
|
|
1393
1796
|
async function __createInstance(
|
|
1394
1797
|
__wasmInput,
|
|
1798
|
+
__options,
|
|
1395
1799
|
__beforeExitDestroy,
|
|
1396
1800
|
__onManagedDestroyer,
|
|
1397
1801
|
) {
|
|
@@ -1401,31 +1805,40 @@ async function __createInstance(
|
|
|
1401
1805
|
version: 'preview1',
|
|
1402
1806
|
})
|
|
1403
1807
|
// The wasm module is linked with \`--import-memory\`, so a Memory must be
|
|
1404
|
-
// provided. It is
|
|
1405
|
-
//
|
|
1406
|
-
//
|
|
1407
|
-
// leak a context that never reaches
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
maximum: ${maximumMemory},
|
|
1411
|
-
})
|
|
1808
|
+
// provided. It is resolved here in function scope (workerd bans global scope
|
|
1809
|
+
// allocation) and is never shared (no threads, no SharedArrayBuffer).
|
|
1810
|
+
// Resolve it before the emnapi context so a rejected option bag or a host
|
|
1811
|
+
// memory-limit failure cannot leak a context that never reaches
|
|
1812
|
+
// instantiation.
|
|
1813
|
+
const __wasmMemory = __resolveInstanceMemory(__options)
|
|
1412
1814
|
let __lifecycleState = 'pending'
|
|
1413
1815
|
let __destroyEmnapiContext
|
|
1414
1816
|
let __destroyOwnedContext
|
|
1415
1817
|
let __destroyManagedOwnedContext
|
|
1416
1818
|
let __napiInstance
|
|
1819
|
+
${instanceHostState}\
|
|
1417
1820
|
let __wasmEnvCleanupRan = false
|
|
1418
1821
|
let __wasmEnvCleanupPrepared = false
|
|
1822
|
+
let __wasmEnvCleanupPreparing = false
|
|
1419
1823
|
let __wasmEnvCleanupDrained = false
|
|
1420
1824
|
let __wasmEnvCleanupDrainPromise
|
|
1825
|
+
const __isPreparingEnvCleanup = () => __wasmEnvCleanupPreparing
|
|
1421
1826
|
const __prepareEnvCleanup = () => {
|
|
1422
|
-
if (__wasmEnvCleanupPrepared) {
|
|
1827
|
+
if (__wasmEnvCleanupPrepared || __wasmEnvCleanupPreparing) {
|
|
1423
1828
|
return
|
|
1424
1829
|
}
|
|
1425
1830
|
const __prepareWasmEnvCleanup =
|
|
1426
1831
|
__napiInstance?.exports.napi_prepare_wasm_env_cleanup
|
|
1427
1832
|
if (typeof __prepareWasmEnvCleanup === 'function') {
|
|
1428
|
-
|
|
1833
|
+
// The addon settles the promises it cancels synchronously, under a
|
|
1834
|
+
// non-reentrant lifecycle mutex: anything a promise hook calls from in
|
|
1835
|
+
// here must not reach this export again.
|
|
1836
|
+
__wasmEnvCleanupPreparing = true
|
|
1837
|
+
try {
|
|
1838
|
+
__prepareWasmEnvCleanup()
|
|
1839
|
+
} finally {
|
|
1840
|
+
__wasmEnvCleanupPreparing = false
|
|
1841
|
+
}
|
|
1429
1842
|
__wasmEnvCleanupRan = true
|
|
1430
1843
|
}
|
|
1431
1844
|
__wasmEnvCleanupPrepared = true
|
|
@@ -1472,6 +1885,63 @@ async function __createInstance(
|
|
|
1472
1885
|
__wasmEnvCleanupDrainPromise = __tracked
|
|
1473
1886
|
return __tracked
|
|
1474
1887
|
}
|
|
1888
|
+
let __disposed = false
|
|
1889
|
+
const __runInstanceDisposal = async () => {
|
|
1890
|
+
if (__lifecycleState !== 'failed') {
|
|
1891
|
+
__lifecycleState = 'disposal'
|
|
1892
|
+
}
|
|
1893
|
+
// Settle what the barrier cancelled before the environment stops
|
|
1894
|
+
// accepting JavaScript calls. Undefined unless something is queued, so
|
|
1895
|
+
// an idle disposal is not delayed by a single turn.
|
|
1896
|
+
const __drained = __prepareForDisposal()
|
|
1897
|
+
if (__drained) {
|
|
1898
|
+
await __drained
|
|
1899
|
+
}
|
|
1900
|
+
const __result = await (__beforeExitDestroy
|
|
1901
|
+
? __destroyManagedOwnedContext()
|
|
1902
|
+
: __destroyOwnedContext())
|
|
1903
|
+
// Only a completed destroy retires the instance; a throw above leaves
|
|
1904
|
+
// the counter untouched so a retried dispose() cannot double-decrement.
|
|
1905
|
+
if (!__disposed) {
|
|
1906
|
+
__disposed = true
|
|
1907
|
+
__liveInstances -= 1
|
|
1908
|
+
}
|
|
1909
|
+
return __result
|
|
1910
|
+
}
|
|
1911
|
+
let __instanceDisposePromise
|
|
1912
|
+
/**
|
|
1913
|
+
* The disposal frame runs the barrier, and the barrier settles the promises it
|
|
1914
|
+
* cancels synchronously — so a promise hook firing inside it can call this
|
|
1915
|
+
* same instance's dispose() again while the first call is still in its drain.
|
|
1916
|
+
* That nested call finds the barrier flagged in flight, prepares nothing,
|
|
1917
|
+
* drains nothing, and falls straight through to the context destroyer, whose
|
|
1918
|
+
* \`Context.destroy()\` hits the wrapper's in-flight no-op. It would record a
|
|
1919
|
+
* destruction that never happened, and the outer frame would then skip the
|
|
1920
|
+
* real one: both disposals resolve, no cleanup hook runs, the context stays
|
|
1921
|
+
* retained.
|
|
1922
|
+
*
|
|
1923
|
+
* Memoize before any of that starts, exactly like the eager loaders'
|
|
1924
|
+
* \`__disposeWasiBinding\`, so there is only ever one disposal frame per
|
|
1925
|
+
* instance and a reentrant caller awaits it instead of racing it. Cleared on
|
|
1926
|
+
* rejection: a drain that failed has to stay retryable.
|
|
1927
|
+
*/
|
|
1928
|
+
const __disposeInstance = () => {
|
|
1929
|
+
if (__instanceDisposePromise) {
|
|
1930
|
+
return __instanceDisposePromise
|
|
1931
|
+
}
|
|
1932
|
+
let __resolveDispose
|
|
1933
|
+
let __rejectDispose
|
|
1934
|
+
const __disposePromise = new Promise((__resolve, __reject) => {
|
|
1935
|
+
__resolveDispose = __resolve
|
|
1936
|
+
__rejectDispose = __reject
|
|
1937
|
+
})
|
|
1938
|
+
__instanceDisposePromise = __disposePromise
|
|
1939
|
+
__runInstanceDisposal().then(__resolveDispose, (__error) => {
|
|
1940
|
+
__instanceDisposePromise = undefined
|
|
1941
|
+
__rejectDispose(__error)
|
|
1942
|
+
})
|
|
1943
|
+
return __disposePromise
|
|
1944
|
+
}
|
|
1475
1945
|
const __destroyBeforeExit = __beforeExitDestroy
|
|
1476
1946
|
? async () => {
|
|
1477
1947
|
if (__lifecycleState === 'failed') {
|
|
@@ -1497,7 +1967,10 @@ async function __createInstance(
|
|
|
1497
1967
|
destroy,
|
|
1498
1968
|
destroyForModuleLifecycle,
|
|
1499
1969
|
registerCleanup: __registerCleanup,
|
|
1500
|
-
} = await __createManagedEmnapiContext(
|
|
1970
|
+
} = await __createManagedEmnapiContext(
|
|
1971
|
+
__prepareEnvCleanup,
|
|
1972
|
+
__isPreparingEnvCleanup,
|
|
1973
|
+
${managedHostDisposeArg} )
|
|
1501
1974
|
__destroyEmnapiContext = destroy
|
|
1502
1975
|
__destroyOwnedContext = () => __destroyEmnapiContext()
|
|
1503
1976
|
__destroyManagedOwnedContext = destroyForModuleLifecycle
|
|
@@ -1534,26 +2007,34 @@ ${emnapiInjectBuffer}\
|
|
|
1534
2007
|
}
|
|
1535
2008
|
},
|
|
1536
2009
|
}))
|
|
2010
|
+
${installInstanceHosts}\
|
|
2011
|
+
// \`instantiate()\` and \`createInstance().exports\` hand out this object; a
|
|
2012
|
+
// named module export does not travel with it. After the instance host
|
|
2013
|
+
// install, which hands the same object to addon-provided registration
|
|
2014
|
+
// functions that may put anything on it, and inside this \`try\`, so a
|
|
2015
|
+
// claimed name flips \`__lifecycleState\` to 'failed' and tears the instance
|
|
2016
|
+
// down rather than escaping a half-built one.
|
|
2017
|
+
${NAPI_BINDING_TARGET_STAMP_FN}(__napiModule.exports, __napiBindingTarget)
|
|
1537
2018
|
if (__lifecycleState === 'pending') {
|
|
1538
2019
|
__lifecycleState = 'succeeded'
|
|
1539
2020
|
}
|
|
2021
|
+
__createdInstances += 1
|
|
2022
|
+
__liveInstances += 1
|
|
1540
2023
|
return {
|
|
1541
2024
|
exports: __napiModule.exports,
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
//
|
|
1547
|
-
//
|
|
1548
|
-
//
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
return __beforeExitDestroy
|
|
1554
|
-
? __destroyManagedOwnedContext()
|
|
1555
|
-
: __destroyOwnedContext()
|
|
2025
|
+
get memory() {
|
|
2026
|
+
return __wasmMemory
|
|
2027
|
+
},
|
|
2028
|
+
get memoryBytes() {
|
|
2029
|
+
// The Memory outlives the environment, so this stays readable after a
|
|
2030
|
+
// FAILED dispose() (which leaves the instance undisposed and
|
|
2031
|
+
// retryable). It reports 0 only once disposal has actually completed.
|
|
2032
|
+
return __disposed ? 0 : __wasmMemory.buffer.byteLength
|
|
2033
|
+
},
|
|
2034
|
+
get disposed() {
|
|
2035
|
+
return __disposed
|
|
1556
2036
|
},
|
|
2037
|
+
dispose: __disposeInstance,
|
|
1557
2038
|
}
|
|
1558
2039
|
} catch (error) {
|
|
1559
2040
|
__lifecycleState = 'failed'
|
|
@@ -1625,11 +2106,22 @@ ${emnapiInjectBuffer}\
|
|
|
1625
2106
|
}
|
|
1626
2107
|
|
|
1627
2108
|
/**
|
|
1628
|
-
* Create an independent instance. Call dispose() when the instance
|
|
1629
|
-
* longer needed so emnapi cleanup hooks run deterministically.
|
|
2109
|
+
* Create an independent instance. Call and await dispose() when the instance
|
|
2110
|
+
* is no longer needed so emnapi cleanup hooks run deterministically.
|
|
2111
|
+
*
|
|
2112
|
+
* The optional second argument selects this instance's linear memory: either
|
|
2113
|
+
* \`memory\` (an unshared, single-use WebAssembly.Memory you allocated) or
|
|
2114
|
+
* \`initialMemoryPages\` / \`maximumMemoryPages\`, never both. Omitted, the
|
|
2115
|
+
* loader allocates WASM_MEMORY.initialPages..WASM_MEMORY.maximumPages.
|
|
2116
|
+
*
|
|
2117
|
+
* A provided Memory must come from this loader's own realm: the WASI and
|
|
2118
|
+
* emnapi layers underneath identify one with a realm-local \`instanceof\`, so a
|
|
2119
|
+
* Memory built in a \`node:vm\` context or another frame is rejected. Every
|
|
2120
|
+
* Memory an instance runs on is single-use, the loader-allocated one included:
|
|
2121
|
+
* \`instance.memory\` cannot be recycled into a second \`createInstance()\`.
|
|
1630
2122
|
*/
|
|
1631
|
-
export async function createInstance(__wasmInput) {
|
|
1632
|
-
return __createInstance(__wasmInput)
|
|
2123
|
+
export async function createInstance(__wasmInput, __options) {
|
|
2124
|
+
return __createInstance(__wasmInput, __options)
|
|
1633
2125
|
}
|
|
1634
2126
|
|
|
1635
2127
|
let __defaultModulePromise
|
|
@@ -1666,6 +2158,7 @@ export function instantiate(__wasmInput) {
|
|
|
1666
2158
|
const __instancePromise = __modulePromise.then((__module) =>
|
|
1667
2159
|
__createInstance(
|
|
1668
2160
|
__module,
|
|
2161
|
+
undefined,
|
|
1669
2162
|
__disposeDefaultInstance,
|
|
1670
2163
|
(__managedDestroyer) => {
|
|
1671
2164
|
__defaultManagedDestroyers.set(
|
|
@@ -1814,21 +2307,90 @@ export function dispose() {
|
|
|
1814
2307
|
|
|
1815
2308
|
export const createWasiDeferredBrowserBindingTypeDef = (
|
|
1816
2309
|
packageName: string,
|
|
2310
|
+
platformArchABI = 'wasm32-wasip1',
|
|
1817
2311
|
) => `export type WasiBinding = typeof import('${packageName}')
|
|
1818
2312
|
|
|
1819
2313
|
export type WasiModuleInput =
|
|
1820
2314
|
| WebAssembly.Module
|
|
1821
2315
|
| PromiseLike<WebAssembly.Module>
|
|
1822
2316
|
|
|
2317
|
+
/** Run the instance on a linear memory the caller allocated. */
|
|
2318
|
+
export interface WasiCallerMemoryOptions {
|
|
2319
|
+
/**
|
|
2320
|
+
* A caller-allocated linear memory for this instance. It must be unshared
|
|
2321
|
+
* and created in this loader's own realm — the WASI and emnapi layers
|
|
2322
|
+
* underneath identify a Memory with a realm-local \`instanceof\`, so one from
|
|
2323
|
+
* a \`node:vm\` context or another frame is rejected. It is single-use: once
|
|
2324
|
+
* a validated initialization attempt has begun, the same Memory cannot be
|
|
2325
|
+
* passed again — including after that attempt failed, and after the instance
|
|
2326
|
+
* was disposed.
|
|
2327
|
+
*/
|
|
2328
|
+
memory: WebAssembly.Memory
|
|
2329
|
+
/** Not available beside \`memory\`: the loader allocates neither. */
|
|
2330
|
+
initialMemoryPages?: never
|
|
2331
|
+
/** Not available beside \`memory\`: the loader allocates neither. */
|
|
2332
|
+
maximumMemoryPages?: never
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
/** Let the loader allocate the linear memory, optionally sized. */
|
|
2336
|
+
export interface WasiAllocatedMemoryOptions {
|
|
2337
|
+
/** Not available beside the page counts: they size the loader's own Memory. */
|
|
2338
|
+
memory?: never
|
|
2339
|
+
/** @default WASM_MEMORY.initialPages */
|
|
2340
|
+
initialMemoryPages?: number
|
|
2341
|
+
/** @default WASM_MEMORY.maximumPages */
|
|
2342
|
+
maximumMemoryPages?: number
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
/**
|
|
2346
|
+
* Either memory form, never a mix of the two: the loader throws a TypeError
|
|
2347
|
+
* on \`memory\` beside a page count. \`{}\` and an omitted argument select the
|
|
2348
|
+
* loader defaults.
|
|
2349
|
+
*/
|
|
2350
|
+
export type WasiInstanceOptions =
|
|
2351
|
+
| WasiCallerMemoryOptions
|
|
2352
|
+
| WasiAllocatedMemoryOptions
|
|
2353
|
+
|
|
2354
|
+
export interface WasiRuntimeStats {
|
|
2355
|
+
/** Instances created by this evaluated loader module, not process-wide. */
|
|
2356
|
+
createdInstances: number
|
|
2357
|
+
/** Created instances whose dispose() has not completed. */
|
|
2358
|
+
liveInstances: number
|
|
2359
|
+
/** Declared initial address space, not committed memory. */
|
|
2360
|
+
declaredInitialMemoryBytes: number
|
|
2361
|
+
}
|
|
2362
|
+
|
|
1823
2363
|
export interface WasiInstance {
|
|
1824
2364
|
readonly exports: WasiBinding
|
|
2365
|
+
/** This instance's linear memory. Claimed, so it cannot start another one. */
|
|
2366
|
+
readonly memory: WebAssembly.Memory
|
|
2367
|
+
/** Current linear-memory size; 0 once dispose() has completed. */
|
|
2368
|
+
readonly memoryBytes: number
|
|
2369
|
+
readonly disposed: boolean
|
|
1825
2370
|
dispose(): Promise<void>
|
|
1826
2371
|
}
|
|
1827
2372
|
|
|
2373
|
+
/** The memory descriptor compiled into this loader. */
|
|
2374
|
+
export const WASM_MEMORY: Readonly<{
|
|
2375
|
+
initialPages: number
|
|
2376
|
+
maximumPages: number
|
|
2377
|
+
pageBytes: number
|
|
2378
|
+
initialBytes: number
|
|
2379
|
+
maximumBytes: number
|
|
2380
|
+
}>
|
|
2381
|
+
|
|
2382
|
+
export function getDeferredRuntimeStats(): Readonly<WasiRuntimeStats>
|
|
2383
|
+
|
|
1828
2384
|
export function instantiate(wasmInput: WasiModuleInput): Promise<WasiBinding>
|
|
1829
|
-
export function createInstance(
|
|
2385
|
+
export function createInstance(
|
|
2386
|
+
wasmInput: WasiModuleInput,
|
|
2387
|
+
options?: WasiInstanceOptions,
|
|
2388
|
+
): Promise<WasiInstance>
|
|
1830
2389
|
/** Dispose the singleton and retry retained failed-initialization cleanup. */
|
|
1831
2390
|
export function dispose(): Promise<void>
|
|
2391
|
+
|
|
2392
|
+
/** The WASI flavor this deferred loader instantiates. */
|
|
2393
|
+
export declare const __napiBindingTarget: '${platformArchABI}'
|
|
1832
2394
|
`
|
|
1833
2395
|
|
|
1834
2396
|
export const createWasiBinding = (
|
|
@@ -1842,7 +2404,20 @@ export const createWasiBinding = (
|
|
|
1842
2404
|
// wasm artifact.
|
|
1843
2405
|
platformArchABI = 'wasm32-wasi',
|
|
1844
2406
|
packageWasmFileName = wasmFileName,
|
|
2407
|
+
asyncRuntime = false,
|
|
1845
2408
|
) => {
|
|
2409
|
+
const asyncRuntimeImport = asyncRuntime
|
|
2410
|
+
? `const {
|
|
2411
|
+
installCurrentThreadHosts: __installCurrentThreadHosts,
|
|
2412
|
+
} = require('@napi-rs/async-runtime')
|
|
2413
|
+
`
|
|
2414
|
+
: ''
|
|
2415
|
+
const installAsyncRuntimeHosts = asyncRuntime
|
|
2416
|
+
? ` __currentThreadHostsDisposer = __installCurrentThreadHosts(
|
|
2417
|
+
__napiModule.exports,
|
|
2418
|
+
)
|
|
2419
|
+
`
|
|
2420
|
+
: ''
|
|
1846
2421
|
const workerImports = threads
|
|
1847
2422
|
? `const { Worker } = require('node:worker_threads')
|
|
1848
2423
|
`
|
|
@@ -1923,6 +2498,7 @@ function __createWasiWorker(filename) {
|
|
|
1923
2498
|
return new Worker(filename, {
|
|
1924
2499
|
env: process.env,
|
|
1925
2500
|
execArgv: __workerExecArgv,
|
|
2501
|
+
workerData: { hostRoot: __hostRoot, rootDir: __rootDir },
|
|
1926
2502
|
})
|
|
1927
2503
|
} catch (error) {
|
|
1928
2504
|
if (!error || error.code !== 'ERR_WORKER_INVALID_EXEC_ARGV') {
|
|
@@ -1944,7 +2520,7 @@ function __createWasiWorker(filename) {
|
|
|
1944
2520
|
: ''
|
|
1945
2521
|
const memoryName = threads ? '__sharedMemory' : '__wasmMemory'
|
|
1946
2522
|
const asyncWorkOptions = threads
|
|
1947
|
-
? ` asyncWorkPoolSize: (function() {
|
|
2523
|
+
? ` asyncWorkPoolSize: (function () {
|
|
1948
2524
|
const threadsSizeFromEnv = Number(process.env.NAPI_RS_ASYNC_WORK_POOL_SIZE ?? process.env.UV_THREADPOOL_SIZE)
|
|
1949
2525
|
// NaN > 0 is false
|
|
1950
2526
|
if (threadsSizeFromEnv > 0) {
|
|
@@ -1980,21 +2556,21 @@ function __createWasiWorker(filename) {
|
|
|
1980
2556
|
// According to https://github.com/nodejs/node/blob/19e0d472728c79d418b74bddff588bea70a403d0/lib/internal/worker.js#L415,
|
|
1981
2557
|
// a worker is consist of two handles: kPublicPort and kHandle.
|
|
1982
2558
|
{
|
|
1983
|
-
const kPublicPort = Object.getOwnPropertySymbols(worker).find(s =>
|
|
1984
|
-
s.toString().includes(
|
|
1985
|
-
)
|
|
2559
|
+
const kPublicPort = Object.getOwnPropertySymbols(worker).find((s) =>
|
|
2560
|
+
s.toString().includes('kPublicPort'),
|
|
2561
|
+
)
|
|
1986
2562
|
if (kPublicPort) {
|
|
1987
|
-
worker[kPublicPort].ref = () => {}
|
|
2563
|
+
worker[kPublicPort].ref = () => {}
|
|
1988
2564
|
}
|
|
1989
2565
|
|
|
1990
|
-
const kHandle = Object.getOwnPropertySymbols(worker).find(s =>
|
|
1991
|
-
s.toString().includes(
|
|
1992
|
-
)
|
|
2566
|
+
const kHandle = Object.getOwnPropertySymbols(worker).find((s) =>
|
|
2567
|
+
s.toString().includes('kHandle'),
|
|
2568
|
+
)
|
|
1993
2569
|
if (kHandle) {
|
|
1994
|
-
worker[kHandle].ref = () => {}
|
|
2570
|
+
worker[kHandle].ref = () => {}
|
|
1995
2571
|
}
|
|
1996
2572
|
|
|
1997
|
-
worker.unref()
|
|
2573
|
+
worker.unref()
|
|
1998
2574
|
}
|
|
1999
2575
|
return worker
|
|
2000
2576
|
},
|
|
@@ -2002,10 +2578,11 @@ function __createWasiWorker(filename) {
|
|
|
2002
2578
|
: ''
|
|
2003
2579
|
|
|
2004
2580
|
return `/* eslint-disable */
|
|
2005
|
-
/* prettier-ignore */
|
|
2006
|
-
|
|
2007
2581
|
/* auto-generated by NAPI-RS */
|
|
2008
2582
|
|
|
2583
|
+
const __napiBindingTarget = '${platformArchABI}'
|
|
2584
|
+
${BINDING_TARGET_STAMP_HELPER}
|
|
2585
|
+
|
|
2009
2586
|
const __nodeFs = require('node:fs')
|
|
2010
2587
|
const __nodePath = require('node:path')
|
|
2011
2588
|
const { WASI: __nodeWASI } = require('node:wasi')
|
|
@@ -2017,16 +2594,21 @@ ${workerRuntimeImport}\
|
|
|
2017
2594
|
instantiateNapiModuleSync: __emnapiInstantiateNapiModuleSync,
|
|
2018
2595
|
} = require('@napi-rs/wasm-runtime')
|
|
2019
2596
|
const { createContext: __emnapiCreateContext } = require('@emnapi/runtime')
|
|
2597
|
+
${asyncRuntimeImport}\
|
|
2020
2598
|
${workerExecArgv}\
|
|
2021
2599
|
|
|
2022
|
-
const
|
|
2600
|
+
const __cwd = process.cwd()
|
|
2601
|
+
const __rootDir = __nodePath.parse(__cwd).root
|
|
2602
|
+
const __hostRoot =
|
|
2603
|
+
process.platform === 'android' ? __cwd : __rootDir
|
|
2023
2604
|
|
|
2024
2605
|
const __wasi = new __nodeWASI({
|
|
2025
2606
|
version: 'preview1',
|
|
2026
2607
|
env: process.env,
|
|
2027
2608
|
preopens: {
|
|
2028
|
-
[__rootDir]:
|
|
2029
|
-
|
|
2609
|
+
[__rootDir]: __hostRoot,
|
|
2610
|
+
[__hostRoot]: __hostRoot,
|
|
2611
|
+
},
|
|
2030
2612
|
})
|
|
2031
2613
|
|
|
2032
2614
|
const ${memoryName} = new WebAssembly.Memory({
|
|
@@ -2056,7 +2638,7 @@ if (__nodeFs.existsSync(__wasmDebugFilePath)) {
|
|
|
2056
2638
|
|
|
2057
2639
|
const __wasmFile = __nodeFs.readFileSync(__wasmFilePath)
|
|
2058
2640
|
let __emnapiContext
|
|
2059
|
-
${
|
|
2641
|
+
${createEmnapiContextLifecycle(asyncRuntime)}
|
|
2060
2642
|
const __wasiRollbackRegistrySymbol = Symbol.for('${WASI_ROLLBACK_REGISTRY_SYMBOL}')
|
|
2061
2643
|
const __wasiRollbackRegistryKey =
|
|
2062
2644
|
typeof __filename === 'string' ? __filename : __wasmFilePath
|
|
@@ -2230,7 +2812,11 @@ function __captureEmnapiAutoDestroyListener() {
|
|
|
2230
2812
|
try {
|
|
2231
2813
|
const __finishAutoDestroyCapture = __captureEmnapiAutoDestroyListener()
|
|
2232
2814
|
try {
|
|
2233
|
-
__emnapiContext =
|
|
2815
|
+
__emnapiContext = __wrapEmnapiContextDestroyForSettlement(
|
|
2816
|
+
__emnapiCreateContext({ autoDestroy: false }),
|
|
2817
|
+
__prepareWasmEnvCleanup,
|
|
2818
|
+
__isPreparingWasmEnvCleanup,
|
|
2819
|
+
)
|
|
2234
2820
|
// emnapi 2.x still registers an unconditional once-listener for
|
|
2235
2821
|
// beforeExit that auto-destroys the context, and suppressDestroy() only
|
|
2236
2822
|
// neutralizes its callback without removing it. This loader owns cleanup
|
|
@@ -2270,6 +2856,23 @@ ${workerOption}\
|
|
|
2270
2856
|
},
|
|
2271
2857
|
}))
|
|
2272
2858
|
__publishWasiDispose(__napiModule.exports)
|
|
2859
|
+
${installAsyncRuntimeHosts}\
|
|
2860
|
+
// The CommonJS tail below aliases \`__napiModule.exports\`; a named module
|
|
2861
|
+
// export does not travel with it, so carry the marker on the binding itself
|
|
2862
|
+
// too. Three things pin the stamp to exactly this spot:
|
|
2863
|
+
// - inside this \`try\`, because the guard throws on a
|
|
2864
|
+
// \`#[napi(module_exports)]\` hook that claimed the name, and only the
|
|
2865
|
+
// catch below tears the environment — context, workers, exit listener —
|
|
2866
|
+
// back down;
|
|
2867
|
+
// - after the async runtime host install, which hands this same object to
|
|
2868
|
+
// addon-provided registration functions that may put anything on it;
|
|
2869
|
+
// - assigning onto the loader's own \`module.exports\`, which is still the
|
|
2870
|
+
// original object here, so an addon accessor with a refusing setter is
|
|
2871
|
+
// never written through. \`cjs-module-lexer\` — Node's CJS -> ESM named
|
|
2872
|
+
// export detection — reads the static \`module.exports.<name> =\` either
|
|
2873
|
+
// way, and the later \`module.exports = __napiModule.exports\` does not
|
|
2874
|
+
// undo that.
|
|
2875
|
+
module.exports.${NAPI_BINDING_TARGET_EXPORT} = ${NAPI_BINDING_TARGET_STAMP_FN}(__napiModule.exports, __napiBindingTarget)
|
|
2273
2876
|
__registerWasiExitListener()
|
|
2274
2877
|
} catch (error) {
|
|
2275
2878
|
const rollback = {
|