@@ -2,16 +2,17 @@ import { BEGIN, CallContext, END, ENV, GET_BLOCK, RESET, SET_HOST_CONTEXT, STORE
2
2
import { type InternalConfig , InternalWasi , PluginOutput } from './interfaces.ts' ;
3
3
import { CAPABILITIES } from './polyfills/deno-capabilities.ts' ;
4
4
import { loadWasi } from './polyfills/deno-wasi.ts' ;
5
+ import { HttpContext } from './http-context.ts' ;
5
6
6
7
export const EXTISM_ENV = 'extism:host/env' ;
7
8
8
9
type InstantiatedModule = [ WebAssembly . Module , WebAssembly . Instance ] ;
9
10
10
11
interface SuspendingCtor {
11
- new ( fn : CallableFunction ) : any ;
12
+ new ( fn : CallableFunction ) : any ;
12
13
}
13
14
14
- const AsyncFunction = ( async ( ) => { } ) . constructor ;
15
+ const AsyncFunction = ( async ( ) => { } ) . constructor ;
15
16
const Suspending : SuspendingCtor | undefined = ( WebAssembly as any ) . Suspending ;
16
17
const promising : CallableFunction | undefined = ( WebAssembly as any ) . promising ;
17
18
@@ -140,7 +141,7 @@ export async function createForegroundPlugin(
140
141
const isAsync = func . constructor === AsyncFunction ;
141
142
suspendsOnInvoke ||= isAsync ;
142
143
const wrapped = func . bind ( null , context ) ;
143
- imports [ namespace ] [ name ] = isAsync ? new ( WebAssembly as any ) . Suspending ( wrapped ) : wrapped ;
144
+ imports [ namespace ] [ name ] = isAsync ? new Suspending ! ( wrapped ) : wrapped ;
144
145
}
145
146
}
146
147
@@ -158,12 +159,14 @@ export async function createForegroundPlugin(
158
159
const seen : Map < WebAssembly . Module , WebAssembly . Instance > = new Map ( ) ;
159
160
const wasiList : InternalWasi [ ] = [ ] ;
160
161
161
- const instance = await instantiateModule ( [ 'main' ] , modules [ mainIndex ] , imports , opts , wasiList , names , modules , seen ) ;
162
+ const mutableFlags = { suspendsOnInvoke }
163
+ const instance = await instantiateModule ( context , [ 'main' ] , modules [ mainIndex ] , imports , opts , wasiList , names , modules , seen , mutableFlags ) ;
162
164
163
- return new ForegroundPlugin ( opts , context , [ modules [ mainIndex ] , instance ] , wasiList , suspendsOnInvoke ) ;
165
+ return new ForegroundPlugin ( opts , context , [ modules [ mainIndex ] , instance ] , wasiList , mutableFlags . suspendsOnInvoke ) ;
164
166
}
165
167
166
168
async function instantiateModule (
169
+ context : CallContext ,
167
170
current : string [ ] ,
168
171
module : WebAssembly . Module ,
169
172
imports : Record < string , Record < string , any > > ,
@@ -172,6 +175,7 @@ async function instantiateModule(
172
175
names : string [ ] ,
173
176
modules : WebAssembly . Module [ ] ,
174
177
linked : Map < WebAssembly . Module , WebAssembly . Instance | null > ,
178
+ mutableFlags : { suspendsOnInvoke : boolean }
175
179
) {
176
180
linked . set ( module , null ) ;
177
181
@@ -216,6 +220,45 @@ async function instantiateModule(
216
220
) ;
217
221
}
218
222
223
+ // XXX(chrisdickinson): This is a bit of a hack, admittedly. So what's going on here?
224
+ //
225
+ // JSPI is going on here. Let me explain: at the time of writing, the js-sdk supports
226
+ // JSPI by detecting AsyncFunction use in the `functions` parameter. When we detect an
227
+ // async function in imports we _must_ mark all exported Wasm functions as "promising" --
228
+ // that is, they might call a host function that suspends the stack.
229
+ //
230
+ // If we were to mark extism's http_request as async, we would _always_ set exports as
231
+ // "promising". This adds unnecessary overhead for folks who aren't using `http_request`.
232
+ // Instead, we detect if any of the manifest items *import* `http_request`. If they
233
+ // haven't overridden the default CallContext implementation, we provide an HttpContext
234
+ // on-demand.
235
+ //
236
+ // Unfortuantely this duplicates a little bit of logic-- in particular, we have to bind
237
+ // CallContext to each of the HttpContext contributions (See "REBIND" below.)
238
+ if (
239
+ module === EXTISM_ENV &&
240
+ name === 'http_request' &&
241
+ promising &&
242
+ imports [ module ] [ name ] === context [ ENV ] . http_request
243
+ ) {
244
+ const httpContext = new HttpContext (
245
+ opts . fetch ,
246
+ opts . allowedHosts ,
247
+ opts . memory ,
248
+ opts . allowHttpResponseHeaders
249
+ ) ;
250
+
251
+ mutableFlags . suspendsOnInvoke = true
252
+
253
+ const contributions = { } as any
254
+ httpContext . contribute ( contributions )
255
+ for ( const [ key , entry ] of Object . entries ( contributions [ EXTISM_ENV ] as { [ k : string ] : CallableFunction } ) ) {
256
+ // REBIND:
257
+ imports [ module ] [ key ] = ( entry as any ) . bind ( null , context )
258
+ }
259
+ imports [ module ] [ name ] = new Suspending ! ( imports [ module ] [ name ] )
260
+ }
261
+
219
262
switch ( kind ) {
220
263
case `function` : {
221
264
instantiationImports [ module ] ??= { } ;
@@ -246,11 +289,11 @@ async function instantiateModule(
246
289
247
290
// If the dependency provides "_start", treat it as a WASI Command module; instantiate it (and its subtree) directly.
248
291
const instance = providerExports . find ( ( xs ) => xs . name === '_start' )
249
- ? await instantiateModule ( [ ...current , module ] , provider , imports , opts , wasiList , names , modules , new Map ( ) )
292
+ ? await instantiateModule ( context , [ ...current , module ] , provider , imports , opts , wasiList , names , modules , new Map ( ) , mutableFlags )
250
293
: ! linked . has ( provider )
251
- ? ( await instantiateModule ( [ ...current , module ] , provider , imports , opts , wasiList , names , modules , linked ) ,
252
- linked . get ( provider ) )
253
- : linked . get ( provider ) ;
294
+ ? ( await instantiateModule ( context , [ ...current , module ] , provider , imports , opts , wasiList , names , modules , linked , mutableFlags ) ,
295
+ linked . get ( provider ) )
296
+ : linked . get ( provider ) ;
254
297
255
298
if ( ! instance ) {
256
299
// circular import, either make a trampoline or bail
@@ -291,10 +334,10 @@ async function instantiateModule(
291
334
const guestType = instance . exports . hs_init
292
335
? 'haskell'
293
336
: instance . exports . _initialize
294
- ? 'reactor'
295
- : instance . exports . _start
296
- ? 'command'
297
- : 'none' ;
337
+ ? 'reactor'
338
+ : instance . exports . _start
339
+ ? 'command'
340
+ : 'none' ;
298
341
299
342
if ( wasi ) {
300
343
await wasi ?. initialize ( instance ) ;
0 commit comments