Skip to main content

js_component_bindgen/intrinsics/
mod.rs

1//! Intrinsics used from JS
2
3use std::collections::{BTreeMap, BTreeSet};
4use std::fmt::Write;
5use std::sync::Mutex;
6
7use crate::source::Source;
8use crate::{TranspileOpts, uwrite, uwriteln};
9
10pub(crate) mod conversion;
11use conversion::ConversionIntrinsic;
12
13pub(crate) mod js_helper;
14use js_helper::JsHelperIntrinsic;
15
16pub(crate) mod webidl;
17use webidl::WebIdlIntrinsic;
18
19pub(crate) mod string;
20use string::StringIntrinsic;
21
22pub(crate) mod resource;
23use resource::ResourceIntrinsic;
24
25pub(crate) mod lift;
26use lift::LiftIntrinsic;
27
28pub(crate) mod lower;
29use lower::LowerIntrinsic;
30
31pub(crate) mod component;
32use component::ComponentIntrinsic;
33
34pub(crate) mod p3;
35use p3::async_future::AsyncFutureIntrinsic;
36use p3::async_stream::AsyncStreamIntrinsic;
37use p3::async_task::AsyncTaskIntrinsic;
38use p3::error_context::ErrCtxIntrinsic;
39use p3::host::HostIntrinsic;
40use p3::waitable::WaitableIntrinsic;
41
42/// List of all intrinsics that are used by these
43///
44/// These intrinsics refer to JS code that is included in order to make
45/// transpiled WebAssembly components and their imports/exports functional
46/// in the relevant JS context.
47#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
48pub enum Intrinsic {
49    JsHelper(JsHelperIntrinsic),
50    WebIdl(WebIdlIntrinsic),
51    Conversion(ConversionIntrinsic),
52    String(StringIntrinsic),
53    Resource(ResourceIntrinsic),
54    ErrCtx(ErrCtxIntrinsic),
55    AsyncTask(AsyncTaskIntrinsic),
56    Waitable(WaitableIntrinsic),
57    Lift(LiftIntrinsic),
58    Lower(LowerIntrinsic),
59    AsyncStream(AsyncStreamIntrinsic),
60    AsyncFuture(AsyncFutureIntrinsic),
61    Component(ComponentIntrinsic),
62    Host(HostIntrinsic),
63
64    // Polyfills
65    PromiseWithResolversPonyfill,
66
67    /// Enable debug logging
68    DebugLog,
69
70    /// Global setting for determinism (used in async)
71    GlobalAsyncDeterminism,
72
73    /// Randomly produce a boolean true/false
74    CoinFlip,
75
76    // Basic type helpers
77    ConstantI32Max,
78    ConstantI32Min,
79    TypeCheckValidI32,
80    TypeCheckAsyncFn,
81    AsyncFunctionCtor,
82
83    Base64Compile,
84    ClampGuest,
85    FetchCompile,
86
87    // Globals
88    SymbolCabiDispose,
89    SymbolCabiLower,
90    SymbolResourceHandle,
91    SymbolResourceRep,
92    SymbolDispose,
93    SymbolAsyncIterator,
94    SymbolIterator,
95    ScopeId,
96    HandleTables,
97
98    /// Class that conforms to a `ReadableStreams`-like interface and is usable externally
99    ///
100    /// This is normally the `ReadableStream` class provided by the platform itself.
101    PlatformReadableStreamClass,
102
103    // Global Initializers
104    FinalizationRegistryCreate,
105
106    // Global classes
107    ComponentError,
108    WebAssemblyRuntimeError,
109
110    // WASI object helpers
111    GetErrorPayload,
112    GetErrorPayloadString,
113
114    /// Class that manages (and synchronizes) writes to managed buffers
115    ManagedBufferClass,
116
117    /// Buffer manager that is used to synchronize component writes
118    BufferManagerClass,
119
120    /// Global for an instantiated buffer manager singleton
121    GlobalBufferManager,
122
123    /// Reusable table structure for holding canonical ABI objects by their representation/identifier of (e.g. resources, waitables, etc)
124    ///
125    /// Representations of objects stored in one of these tables is a u32 (0 is expected to be an invalid index).
126    RepTableClass,
127
128    /// Event codes used for async, as a JS enum
129    AsyncEventCodeEnum,
130
131    // JS helper functions
132    IsLE,
133    ThrowInvalidBool,
134    ThrowUninitialized,
135    HasOwnProperty,
136    InstantiateCore,
137
138    /// Tracking of component memories
139    GlobalComponentMemoryMap,
140
141    /// Tracking of component memories
142    RegisterGlobalMemoryForComponent,
143
144    /// Tracking of component memories
145    LookupMemoriesForComponent,
146
147    /// Global that tracks the current task
148    GlobalCurrentTaskMeta,
149
150    /// Gets the current global task state
151    GetGlobalCurrentTaskMetaFn,
152
153    /// Gets the current global task state
154    SetGlobalCurrentTaskMetaFn,
155
156    /// Execute a closure with a certain set current task
157    WithGlobalCurrentTaskMetaFn,
158
159    /// Execute an async closure with a certain set current task
160    WithGlobalCurrentTaskMetaFnAsync,
161
162    /// Clear the global task meta
163    ClearGlobalCurrentTaskMetaFn,
164
165    /// Wrap the JS payload of a `WebAssembly.Suspending` import so the
166    /// importing component's current-task register survives suspension
167    SuspendingImportWrapperFn,
168}
169
170macro_rules! impl_from_intrinsic {
171    ($($ty:ty => $variant:ident),+ $(,)?) => {
172        $(
173            impl From<$ty> for Intrinsic {
174                fn from(intrinsic: $ty) -> Self {
175                    Self::$variant(intrinsic)
176                }
177            }
178        )+
179    };
180}
181
182impl_from_intrinsic! {
183    JsHelperIntrinsic => JsHelper,
184    WebIdlIntrinsic => WebIdl,
185    ConversionIntrinsic => Conversion,
186    StringIntrinsic => String,
187    ResourceIntrinsic => Resource,
188    ErrCtxIntrinsic => ErrCtx,
189    AsyncTaskIntrinsic => AsyncTask,
190    WaitableIntrinsic => Waitable,
191    LiftIntrinsic => Lift,
192    LowerIntrinsic => Lower,
193    AsyncStreamIntrinsic => AsyncStream,
194    AsyncFutureIntrinsic => AsyncFuture,
195    ComponentIntrinsic => Component,
196    HostIntrinsic => Host,
197}
198
199impl Intrinsic {
200    pub fn render(&self, output: &mut Source, args: &RenderIntrinsicsArgs) {
201        match self {
202            Intrinsic::JsHelper(i) => i.render(output, args),
203            Intrinsic::Conversion(i) => i.render(output, args),
204            Intrinsic::String(i) => i.render(output, args),
205            Intrinsic::ErrCtx(i) => i.render(output, args),
206            Intrinsic::Resource(i) => i.render(output, args),
207            Intrinsic::AsyncTask(i) => i.render(output, args),
208            Intrinsic::Waitable(i) => i.render(output, args),
209            Intrinsic::Lift(i) => i.render(output, args),
210            Intrinsic::Lower(i) => i.render(output, args),
211            Intrinsic::AsyncStream(i) => i.render(output, args),
212            Intrinsic::AsyncFuture(i) => i.render(output, args),
213            Intrinsic::Component(i) => i.render(output, args),
214            Intrinsic::Host(i) => i.render(output, args),
215
216            Intrinsic::GlobalAsyncDeterminism => {
217                uwriteln!(
218                    output,
219                    "const {var_name} = '{determinism}';",
220                    var_name = self.name(),
221                    determinism = args.determinism_profile,
222                );
223            }
224
225            Intrinsic::CoinFlip => {
226                uwriteln!(
227                    output,
228                    "const {var_name} = () => {{ return Math.random() > 0.5; }};",
229                    var_name = self.name(),
230                );
231            }
232
233            Intrinsic::ConstantI32Min => output.push_str(&format!(
234                "const {const_name} = -2_147_483_648;\n",
235                const_name = self.name()
236            )),
237
238            Intrinsic::ConstantI32Max => {
239                uwriteln!(
240                    output,
241                    r#"
242                      const {const_name} = 2_147_483_647;
243                    "#,
244                    const_name = self.name()
245                )
246            }
247
248            Intrinsic::TypeCheckValidI32 => {
249                let i32_const_min = args.require_intrinsic(Intrinsic::ConstantI32Min);
250                let i32_const_max = args.require_intrinsic(Intrinsic::ConstantI32Max);
251
252                uwriteln!(
253                    output,
254                    r#"
255                      const {fn_name} = (n) => typeof n === 'number' && n >= {i32_const_min} && n <= {i32_const_max};
256                    "#,
257                    fn_name = self.name()
258                );
259            }
260
261            Intrinsic::AsyncFunctionCtor => {
262                let async_fn_type = args.require_intrinsic(Intrinsic::AsyncFunctionCtor);
263                uwriteln!(
264                    output,
265                    "const {async_fn_type} = (async () => {{}}).constructor;"
266                );
267            }
268
269            Intrinsic::TypeCheckAsyncFn => {
270                let async_fn_check = args.require_intrinsic(Intrinsic::TypeCheckAsyncFn);
271                let async_fn_ctor = args.require_intrinsic(Intrinsic::AsyncFunctionCtor);
272                uwriteln!(
273                    output,
274                    r#"
275                    const {async_fn_check} = (f) => {{
276                        return f instanceof {async_fn_ctor};
277                    }};
278                    "#,
279                );
280            }
281
282            Intrinsic::Base64Compile => {
283                if !args.transpile_opts.nodejs_compat_disabled {
284                    uwriteln!(
285                        output,
286                        r#"
287                          const base64Compile = str => WebAssembly.compile(
288                              typeof Buffer !== 'undefined'
289                                  ? Buffer.from(str, 'base64')
290                                  : Uint8Array.from(atob(str), b => b.charCodeAt(0))
291                          );
292                        "#
293                    );
294                } else {
295                    uwriteln!(
296                        output,
297                        r#"
298                          const base64Compile = str => WebAssembly.compile(Uint8Array.from(atob(str), b => b.charCodeAt(0)));
299                        "#
300                    );
301                }
302            }
303
304            Intrinsic::ClampGuest => {
305                uwriteln!(
306                    output,
307                    r#"
308                      function clampGuest(i, min, max) {{
309                          if (i < min || i > max) {{
310                              throw new TypeError(`must be between ${{min}} and ${{max}}`);
311                          }}
312                          return i;
313                      }}
314                    "#
315                );
316            }
317
318            Intrinsic::ComponentError => output.push_str(
319                "
320                class ComponentError extends Error {
321                    constructor (value) {
322                        const enumerable = typeof value !== 'string';
323                        super(enumerable ? `${String(value)} (see error.payload)` : value);
324                        Object.defineProperty(this, 'payload', { value, enumerable });
325                    }
326                }
327            ",
328            ),
329
330            Intrinsic::WebAssemblyRuntimeError => {
331                output.push_str("const WebAssemblyRuntimeError = WebAssembly.RuntimeError;\n")
332            }
333
334            Intrinsic::FinalizationRegistryCreate => output.push_str(
335                "
336                function finalizationRegistryCreate (unregister) {
337                    if (typeof FinalizationRegistry === 'undefined') {
338                        return { unregister () {} };
339                    }
340                    return new FinalizationRegistry(unregister);
341                }
342            ",
343            ),
344
345            Intrinsic::FetchCompile => {
346                if !args.transpile_opts.nodejs_compat_disabled {
347                    output.push_str("
348                    const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
349                    let _fs;
350                    async function fetchCompile (url) {
351                        if (isNode) {
352                            _fs = _fs || await import('node:fs/promises');
353                            return WebAssembly.compile(await _fs.readFile(url));
354                        }
355                        return fetch(url).then(WebAssembly.compileStreaming);
356                    }
357                ")
358                } else {
359                    output.push_str(
360                        "
361                    const fetchCompile = url => fetch(url).then(WebAssembly.compileStreaming);
362                ",
363                    )
364                }
365            }
366
367            Intrinsic::GetErrorPayload => {
368                let hop = args.require_intrinsic(Intrinsic::HasOwnProperty);
369                uwrite!(
370                    output,
371                    "
372                    function getErrorPayload(e) {{
373                        if (e && {hop}.call(e, 'payload')) return e.payload;
374                        if (e instanceof Error) throw e;
375                        return e;
376                    }}
377                "
378                )
379            }
380
381            Intrinsic::GetErrorPayloadString => {
382                let hop = args.require_intrinsic(Intrinsic::HasOwnProperty);
383                uwrite!(
384                    output,
385                    "
386                    function getErrorPayloadString(e) {{
387                        if (e && {hop}.call(e, 'payload')) return e.payload;
388                        if (e instanceof Error) return e.message;
389                        return e;
390                    }}
391                "
392                )
393            }
394
395            Intrinsic::WebIdl(w) => w.render(output),
396
397            Intrinsic::HandleTables => {
398                let var_name = self.name();
399                uwriteln!(
400                    output,
401                    r#"
402                      const {var_name} = [];
403                    "#,
404                );
405            }
406
407            Intrinsic::HasOwnProperty => output.push_str(
408                "
409                const hasOwnProperty = Object.prototype.hasOwnProperty;
410            ",
411            ),
412
413            Intrinsic::InstantiateCore => {
414                if !args.instantiation_occurred {
415                    output.push_str(
416                        "
417                    const instantiateCore = WebAssembly.instantiate;
418                ",
419                    )
420                }
421            }
422
423            Intrinsic::IsLE => output.push_str(
424                "
425                const isLE = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
426            ",
427            ),
428
429            Intrinsic::SymbolCabiDispose => output.push_str(
430                "
431                const symbolCabiDispose = Symbol.for('cabiDispose');
432            ",
433            ),
434
435            Intrinsic::SymbolCabiLower => output.push_str(
436                "
437                const symbolCabiLower = Symbol.for('cabiLower');
438            ",
439            ),
440
441            Intrinsic::ScopeId => {
442                let name = self.name();
443                uwriteln!(output, "let {name} = 0;");
444            }
445
446            Intrinsic::SymbolResourceHandle => output.push_str(
447                "
448                const symbolRscHandle = Symbol('handle');
449            ",
450            ),
451
452            Intrinsic::SymbolResourceRep => output.push_str(
453                "
454                const symbolRscRep = Symbol.for('cabiRep');
455            ",
456            ),
457
458            Intrinsic::SymbolDispose => {
459                let var_name = self.name();
460                uwriteln!(
461                    output,
462                    "const {var_name} = Symbol.dispose || Symbol.for('dispose');"
463                );
464            }
465
466            Intrinsic::SymbolAsyncIterator => {
467                let var_name = self.name();
468                uwriteln!(output, "const {var_name} = Symbol.asyncIterator;");
469            }
470
471            Intrinsic::SymbolIterator => {
472                let var_name = self.name();
473                uwriteln!(output, "const {var_name} = Symbol.iterator;");
474            }
475
476            Intrinsic::ThrowInvalidBool => output.push_str(
477                "
478                function throwInvalidBool() {
479                    throw new TypeError('invalid variant discriminant for bool');
480                }
481            ",
482            ),
483
484            Intrinsic::ThrowUninitialized => output.push_str(
485                "
486                function throwUninitialized() {
487                    throw new TypeError('Wasm uninitialized use `await $init` first');
488                }
489            ",
490            ),
491
492            Intrinsic::DebugLog => {
493                let fn_name = args.require_intrinsic(Intrinsic::DebugLog);
494                output.push_str(&format!(
495                    "
496                    const {fn_name} = (...args) => {{
497                        if (!globalThis?.process?.env?.JCO_DEBUG) {{ return; }}
498                        console.debug(...args);
499                    }};
500                "
501                ));
502            }
503
504            Intrinsic::PromiseWithResolversPonyfill => {
505                let fn_name = self.name();
506                output.push_str(&format!(
507                    r#"
508                    function {fn_name}() {{
509                        if (Promise.withResolvers) {{
510                            return Promise.withResolvers();
511                        }} else {{
512                            let resolve;
513                            let reject;
514                            const promise = new Promise((res, rej) => {{
515                                resolve = res;
516                                reject = rej;
517                            }});
518                            return {{ promise, resolve, reject }};
519                        }}
520                    }}
521                "#
522                ));
523            }
524
525            Intrinsic::AsyncEventCodeEnum => {
526                let name = args.require_intrinsic(Intrinsic::AsyncEventCodeEnum);
527                output.push_str(&format!(
528                    "
529                    const {name} = {{
530                        NONE: 0,
531                        SUBTASK: 1,
532                        STREAM_READ: 2,
533                        STREAM_WRITE: 3,
534                        FUTURE_READ: 4,
535                        FUTURE_WRITE: 5,
536                        TASK_CANCELLED: 6,
537                    }};
538                "
539                ));
540            }
541
542            Intrinsic::ManagedBufferClass => {
543                let debug_log_fn = args.require_intrinsic(Intrinsic::DebugLog);
544                let managed_buffer_class = args.require_intrinsic(Intrinsic::ManagedBufferClass);
545                output.push_str(&format!(
546                    r#"
547                    class {managed_buffer_class} {{
548                        static MAX_LENGTH = 2**28 - 1;
549                        #componentIdx;
550                        #memory;
551
552                        #elemMeta = null;
553
554                        #start;
555                        #ptr;
556                        capacity;
557                        processed = 0;
558
559                        #hostOnlyData; // initial data (only filled out for host-owned)
560
561                        target;
562
563                        constructor(args) {{
564                            if (args.capacity > {managed_buffer_class}.MAX_LENGTH) {{
565                                 throw new Error(`buffer size [${{args.capacity}}] greater than max length`);
566                            }}
567                            if (args.componentIdx === undefined) {{ throw new TypeError('missing/invalid component idx'); }}
568                            if (args.capacity === undefined) {{ throw new TypeError('missing/invalid capacity'); }}
569                            if (!args.elemMeta || typeof args.elemMeta.align32 !== 'number') {{
570                                throw new TypeError('missing/invalid element metadata');
571                            }}
572
573                            if (!args.memory && args.start === undefined && args.data === undefined) {{
574                                throw new TypeError('either memory and start ptr or data must be provided for managed buffers');
575                            }}
576
577                            if (args.memory && args.start == undefined) {{
578                                throw new TypeError('missing/invalid start ptr, depsite memory being present');
579                            }}
580
581                            if (!args.elemMeta.isNone && args.capacity > 0) {{
582                                if (args.start && args.start % args.elemMeta.align32 !== 0) {{
583                                    throw new Error(`invalid alignment: type with 32bit alignment [${{args.elemMeta.align32}}] at starting pointer [${{args.start}}]`);
584                                }}
585                                // TODO: memory lenght bounds check
586                            }}
587
588                            this.#componentIdx = args.componentIdx;
589                            this.#memory = args.memory;
590                            this.#start = args.start;
591                            this.#ptr = this.#start;
592                            this.capacity = args.capacity;
593                            this.#elemMeta = args.elemMeta;
594
595                            if (args.data !== undefined && !Array.isArray(args.data)) {{
596                                throw new TypeError('host-only data must be an array');
597                            }}
598                            this.#hostOnlyData = args.data;
599
600                            this.target = args.target;
601                        }}
602
603                        setTarget(tgt) {{ this.target = tgt; }}
604
605                        remaining() {{
606                            return this.capacity - this.processed;
607                        }}
608
609                        componentIdx() {{ return this.#componentIdx; }}
610
611                        getElemMeta() {{ return this.#elemMeta; }}
612
613                        isHostOwned() {{ return !this.#memory; }}
614
615                        read(count) {{
616                            {debug_log_fn}('[{managed_buffer_class}#read()] args', {{ count }});
617                            if (count === undefined || count <= 0) {{
618                                throw new TypeError(`missing/invalid count [${{count}}]`);
619                            }}
620
621                            const cap = this.capacity;
622                            if (count > cap) {{
623                                throw new Error(`cannot read [${{count}}] elements from buffer with capacity [${{cap}}]`);
624                            }}
625
626                            let values = [];
627                            if (this.#elemMeta.isNone) {{
628                                values = [...new Array(count)].map(() => null);
629                            }} else {{
630                                if (this.isHostOwned()) {{
631                                    values = this.#hostOnlyData.slice(0, count);
632                                    this.#hostOnlyData = this.#hostOnlyData.slice(count);
633                                }} else if (this.#elemMeta.payloadTypeName === 'U8') {{
634                                    values = Array.from(new Uint8Array(this.#memory.buffer, this.#ptr, count));
635                                    this.#ptr += count;
636                                }} else {{
637                                    let currentCount = count;
638                                    let startPtr = this.#ptr;
639                                    if (this.#elemMeta.stringEncoding === undefined) {{
640                                        throw new Error('string encoding unknown during read');
641                                    }}
642                                    let liftCtx = {{
643                                        storagePtr: startPtr,
644                                        memory: this.#memory,
645                                        componentIdx: this.#componentIdx,
646                                        stringEncoding: this.#elemMeta.stringEncoding,
647                                    }};
648                                    if (currentCount < 0) {{ throw new Error('unexpectedly invalid count'); }}
649                                    while (currentCount > 0) {{
650                                        const [value, _ctx] = this.#elemMeta.liftFn(liftCtx);
651                                        values.push(value);
652                                        currentCount -= 1;
653                                    }}
654                                    this.#ptr = liftCtx.storagePtr;
655                                }}
656                            }}
657
658                            this.processed += count;
659                            return values;
660                        }}
661
662                        write(values) {{
663                            {debug_log_fn}('[{managed_buffer_class}#write()] args', {{ values }});
664
665                            if (!Array.isArray(values)) {{ throw new TypeError('values input to write() must be an array'); }}
666                            let rc = this.remaining();
667                            if (values.length > rc) {{
668                                throw new Error(`cannot write [${{values.length}}] elements to managed buffer with remaining capacity [${{rc}}]`);
669                            }}
670
671                            if (this.#elemMeta.isNone) {{
672                                if (!values.every(v => v === null)) {{
673                                    throw new Error('non-null values in write() to unit managed buffer');
674                                }}
675                            }} else {{
676                                if (this.isHostOwned()) {{
677                                    this.#hostOnlyData = this.#hostOnlyData.concat(values);
678                                }} else if (this.#elemMeta.payloadTypeName === 'U8') {{
679                                    new Uint8Array(this.#memory.buffer, this.#ptr, values.length).set(values);
680                                    this.#ptr += values.length;
681                                }} else {{
682                                    let startPtr = this.#ptr;
683                                    if (this.#elemMeta.stringEncoding === undefined) {{
684                                        throw new Error('string encoding unknown during write');
685                                    }}
686
687                                    const lowerCtx = {{
688                                        memory: this.#memory,
689                                        storagePtr: startPtr,
690                                        componentIdx: this.#componentIdx,
691                                        stringEncoding: this.#elemMeta.stringEncoding,
692                                        realloc: this.#elemMeta.getReallocFn?.(),
693                                        getReallocFn: this.#elemMeta.getReallocFn,
694                                    }}
695                                    for (const v of values) {{
696                                        lowerCtx.vals = [v];
697                                        this.#elemMeta.lowerFn(lowerCtx);
698                                    }}
699
700                                    this.#ptr = lowerCtx.storagePtr;
701                                }}
702                            }}
703
704                            this.processed += values.length;
705                        }}
706
707                    }}
708                "#
709                ));
710            }
711
712            Intrinsic::BufferManagerClass => {
713                let debug_log_fn = args.require_intrinsic(Intrinsic::DebugLog);
714                let buffer_manager_class = args.require_intrinsic(Intrinsic::BufferManagerClass);
715                let managed_buffer_class = args.require_intrinsic(Intrinsic::ManagedBufferClass);
716
717                output.push_str(&format!(r#"
718                    class {buffer_manager_class} {{
719                        #buffers = new Map();
720                        #bufferIDs = new Map();
721
722                        // NOTE: componentIdx === -1 indicates the host
723                        getNextBufferID(componentIdx) {{
724                            const current = this.#bufferIDs.get(componentIdx);
725                            if (current === undefined) {{
726                                this.#bufferIDs.set(componentIdx, 1n);
727                                return 1n;
728                            }}
729                            const next = current + 1n;
730                            this.#bufferIDs.set(componentIdx, next);
731                            return next;
732                        }}
733
734                        getBuffer(componentIdx, bufferID) {{
735                            {debug_log_fn}('[{buffer_manager_class}#getBuffer()] args', {{ componentIdx, bufferID }});
736                            return this.#buffers.get(componentIdx)?.get(bufferID);
737                        }}
738
739                        createBuffer(args) {{
740                            {debug_log_fn}('[{buffer_manager_class}#createBuffer()] args', args);
741                            if (!args || typeof args !== 'object') {{ throw new TypeError('missing/invalid argument object'); }}
742
743                            if (args.start === undefined && args.data === undefined) {{
744                                throw new  TypeError('either a starting pointer or initial values must be provided');
745                            }}
746
747                            if (args.start !== undefined && args.componentIdx === undefined) {{ throw new TypeError('missing/invalid component idx'); }}
748                            if (args.count === undefined) {{ throw new TypeError('missing/invalid obj count'); }}
749                            if (!args.elemMeta) {{ throw new TypeError('missing/invalid element metadata for use with managed buffer'); }}
750
751                            const {{ componentIdx, data, start, count }} = args;
752
753                            if (!this.#buffers.has(componentIdx)) {{ this.#buffers.set(componentIdx, new Map()); }}
754                            const instanceBuffers = this.#buffers.get(componentIdx);
755
756                            const nextBufID = this.getNextBufferID(componentIdx);
757
758                            const buffer = new {managed_buffer_class}({{
759                                componentIdx,
760                                memory: args.memory,
761                                start: args.start,
762                                capacity: args.count,
763                                elemMeta: args.elemMeta,
764                                data: args.data,
765                                target: args.target,
766                                stringEncoding: args.stringEncoding,
767                            }});
768
769                            if (instanceBuffers.has(nextBufID)) {{
770                                throw new Error(`managed buffer with ID [${{nextBufID}}] already exists`);
771                            }}
772                            instanceBuffers.set(nextBufID, buffer);
773
774                            return {{ id: nextBufID, buffer }};
775                        }}
776
777                        deleteBuffer(componentIdx, bufferID) {{
778                            {debug_log_fn}('[{buffer_manager_class}#deleteBuffer()] args', {{ componentIdx, bufferID }});
779                            return this.#buffers.get(componentIdx)?.delete(bufferID);
780                        }}
781
782                    }}
783                "#));
784            }
785
786            Intrinsic::GlobalBufferManager => {
787                let global_buffer_manager = args.require_intrinsic(Intrinsic::GlobalBufferManager);
788                let buffer_manager_class = args.require_intrinsic(Intrinsic::BufferManagerClass);
789                output.push_str(&format!(
790                    "const {global_buffer_manager} = new {buffer_manager_class}();"
791                ));
792            }
793
794            Intrinsic::RepTableClass => {
795                let debug_log_fn = args.require_intrinsic(Intrinsic::DebugLog);
796                let rep_table_class = args.require_intrinsic(Intrinsic::RepTableClass);
797                output.push_str(&format!(r#"
798                    class {rep_table_class} {{
799                        // Sentinel marking a freed slot; the freelist link for a freed slot
800                        // lives in the odd cell. This keeps get()/contains()/remove() on freed
801                        // reps well-defined (previously they returned/corrupted freelist links).
802                        static FREE = Symbol('{rep_table_class}.free');
803
804                        #data = [0, null];
805                        #size = 0;
806                        #target;
807
808                        constructor(args) {{
809                            this.target = args?.target;
810                        }}
811
812                        data() {{ return this.#data; }}
813
814                        insert(val) {{
815                            {debug_log_fn}('[{rep_table_class}#insert()] args', {{ val, target: this.target }});
816                            const freeIdx = this.#data[0];
817                            if (freeIdx === 0) {{
818                                this.#data.push(val);
819                                this.#data.push(null);
820                                const rep = (this.#data.length >> 1) - 1;
821                                {debug_log_fn}('[{rep_table_class}#insert()] inserted', {{ val, target: this.target, rep }});
822                                this.#size += 1;
823                                return rep;
824                            }}
825                            const placementIdx = freeIdx << 1;
826                            if (this.#data[placementIdx] !== {rep_table_class}.FREE) {{
827                                throw new Error('corrupt rep table freelist: head does not point at a freed slot');
828                            }}
829                            this.#data[0] = this.#data[placementIdx + 1];
830                            this.#data[placementIdx] = val;
831                            this.#data[placementIdx + 1] = null;
832                            {debug_log_fn}('[{rep_table_class}#insert()] inserted', {{ val, target: this.target, rep: freeIdx }});
833                            this.#size += 1;
834                            return freeIdx;
835                        }}
836
837                        get(rep) {{
838                            {debug_log_fn}('[{rep_table_class}#get()] args', {{ rep, target: this.target }});
839                            if (rep === 0) {{ throw new Error('invalid resource rep during get, (cannot be 0)'); }}
840
841                            const baseIdx = rep << 1;
842                            const val = this.#data[baseIdx];
843                            if (val === {rep_table_class}.FREE) {{ return undefined; }}
844                            return val;
845                        }}
846
847                        contains(rep) {{
848                            {debug_log_fn}('[{rep_table_class}#contains()] args', {{ rep, target: this.target }});
849                            if (rep === 0) {{ throw new Error('invalid resource rep during contains, (cannot be 0)'); }}
850
851                            const baseIdx = rep << 1;
852                            const val = this.#data[baseIdx];
853                            return val !== {rep_table_class}.FREE && !!val;
854                        }}
855
856                        remove(rep) {{
857                            {debug_log_fn}('[{rep_table_class}#remove()] args', {{ rep, target: this.target }});
858                            if (rep === 0) {{ throw new Error('invalid resource rep during remove, (cannot be 0)'); }}
859                            if (this.#data.length === 2) {{ throw new Error('invalid'); }}
860
861                            const baseIdx = rep << 1;
862                            if (baseIdx >= this.#data.length) {{
863                                throw new Error(`invalid rep [${{rep}}] during remove, out of range`);
864                            }}
865                            const val = this.#data[baseIdx];
866                            if (val === {rep_table_class}.FREE) {{
867                                throw new Error(`double removal of rep [${{rep}}] (already freed)`);
868                            }}
869
870                            this.#data[baseIdx] = {rep_table_class}.FREE;
871                            this.#data[baseIdx + 1] = this.#data[0];
872                            this.#data[0] = rep;
873                            this.#size -= 1;
874
875                            return val;
876                        }}
877
878                        size() {{ return this.#size; }}
879
880                        clear() {{
881                            {debug_log_fn}('[{rep_table_class}#clear()] args', {{ rep, target: this.target }});
882                            this.#data = [0, null];
883                        }}
884                    }}
885                "#));
886            }
887
888            Intrinsic::GlobalComponentMemoryMap => {
889                let global_component_memory_map =
890                    args.require_intrinsic(Intrinsic::GlobalComponentMemoryMap);
891                output.push_str(&format!(
892                    "const {global_component_memory_map} = new Map();\n"
893                ));
894            }
895
896            Intrinsic::RegisterGlobalMemoryForComponent => {
897                let global_component_memory_map =
898                    args.require_intrinsic(Intrinsic::GlobalComponentMemoryMap);
899                let register_global_component_memory =
900                    args.require_intrinsic(Intrinsic::RegisterGlobalMemoryForComponent);
901                output.push_str(&format!(
902                    r#"
903                      function {register_global_component_memory}(args) {{
904                          const {{ componentIdx, memory, memoryIdx }} = args ?? {{}};
905                          if (componentIdx === undefined) {{ throw new TypeError('missing component idx'); }}
906                          if (memory === undefined && memoryIdx === undefined) {{ throw new TypeError('missing both memory & memory idx'); }}
907                          let inner = {global_component_memory_map}.get(componentIdx);
908                          if (!inner) {{
909                              inner = {{}};
910                              {global_component_memory_map}.set(componentIdx, inner);
911                          }}
912
913                          inner[memoryIdx] = {{ memory, memoryIdx, componentIdx }};
914                      }}
915                    "#)
916                );
917            }
918
919            Intrinsic::LookupMemoriesForComponent => {
920                let global_component_memory_map =
921                    args.require_intrinsic(Intrinsic::GlobalComponentMemoryMap);
922                let lookup_global_memories_for_component =
923                    args.require_intrinsic(Intrinsic::LookupMemoriesForComponent);
924                output.push_str(&format!(
925                    r#"
926                      function {lookup_global_memories_for_component}(args) {{
927                          const {{ componentIdx }} = args ?? {{}};
928                          if (args.componentIdx === undefined) {{ throw new TypeError("missing component idx"); }}
929
930                          const metas = {global_component_memory_map}.get(componentIdx);
931                          if (!metas) {{ return []; }}
932
933                          if (args.memoryIdx === undefined) {{
934                              return Object.values(metas);
935                          }}
936
937                          const meta = metas[args.memoryIdx];
938                          return meta?.memory;
939                      }}
940                    "#)
941                );
942            }
943
944            Self::GlobalCurrentTaskMeta => {
945                let name = self.name();
946                output.push_str(&format!("const {name} = {{}};\n"));
947            }
948
949            Self::GetGlobalCurrentTaskMetaFn => {
950                let get_current_global_task_meta_fn =
951                    args.require_intrinsic(Self::GetGlobalCurrentTaskMetaFn);
952                let global_current_task_meta_obj =
953                    args.require_intrinsic(Self::GlobalCurrentTaskMeta);
954
955                uwriteln!(
956                    output,
957                    r#"
958                      function {get_current_global_task_meta_fn}(componentIdx) {{
959                          if (componentIdx === null || componentIdx === undefined) {{
960                              throw new Error("missing/invalid component idx");
961                          }}
962                          const v = {global_current_task_meta_obj}[componentIdx];
963                          if (v === undefined || v === null) {{
964                              return undefined;
965                          }}
966                          return {{ ...v }};
967                      }}
968                    "#,
969                );
970            }
971
972            Self::SetGlobalCurrentTaskMetaFn => {
973                let set_global_current_task_meta_fn = self.name();
974                let global_current_task_meta_obj =
975                    args.require_intrinsic(Self::GlobalCurrentTaskMeta);
976
977                uwriteln!(
978                    output,
979                    r#"
980                      function {set_global_current_task_meta_fn}(args) {{
981                          if (!args) {{ throw new TypeError('args missing'); }}
982                          if (args.taskID === undefined) {{ throw new TypeError('missing task ID'); }}
983                          if (args.componentIdx === undefined) {{ throw new TypeError('missing component idx'); }}
984                          const {{ taskID, componentIdx }} = args;
985                          return {global_current_task_meta_obj}[componentIdx] = {{ taskID, componentIdx }};
986                      }}
987                    "#,
988                );
989            }
990
991            Self::WithGlobalCurrentTaskMetaFn => {
992                let debug_log_fn = args.require_intrinsic(Intrinsic::DebugLog);
993                let with_global_current_task_meta_fn =
994                    args.require_intrinsic(Self::WithGlobalCurrentTaskMetaFn);
995                let global_current_task_meta_obj =
996                    args.require_intrinsic(Self::GlobalCurrentTaskMeta);
997
998                output.push_str(&format!(
999                    r#"
1000                      function {with_global_current_task_meta_fn}(args) {{
1001                          {debug_log_fn}('[{with_global_current_task_meta_fn}()] args', args);
1002                          if (!args) {{ throw new TypeError('args missing'); }}
1003                          if (args.taskID === undefined) {{ throw new TypeError('missing task ID'); }}
1004                          if (args.componentIdx === undefined) {{ throw new TypeError('missing component idx'); }}
1005                          if (!args.fn) {{ throw new TypeError('missing fn'); }}
1006                          const {{ taskID, componentIdx, fn }} = args;
1007
1008                          try {{
1009                              {global_current_task_meta_obj}[componentIdx] = {{ taskID, componentIdx }};
1010                              return fn();
1011                          }} catch (err) {{
1012                              {debug_log_fn}("error while executing sync callee/callback", {{
1013                                  ...args,
1014                                  err,
1015                              }});
1016                              throw err;
1017                          }} finally {{
1018                              {global_current_task_meta_obj}[componentIdx] = null;
1019                          }}
1020                      }}
1021                    "#,
1022                ));
1023            }
1024
1025            // NOTE: this function wrapper/closure intrinsic essentially acts as a
1026            // defactor task queue, ensuring that the right "current task" is set when
1027            // callees and/or callbacks (WebAssembly functions) run.
1028            //
1029            // The idea here is to avoid creating *our own* centralized task queue/event loop,
1030            // and allow the underlying JS runtime (NodeJS, Browser) to do it's normal scheduling.
1031            //
1032            // This costs us complexity -- an `await`/`.then()`/etc anywhere else could park a
1033            // runtime task and bring us here, in which case we'd be executing *right* before a completely
1034            // unrelated task (this matters most when it's multiple tasks in the same component idx)
1035            //
1036            // e.g.:
1037            // 1. [componentIdx 1, task 2] entered -- it's async so this is an `await task.enter()`
1038            // 2. JS runtime switches away from that task
1039            // 3. [componentIdx 1, task 1] already running, and is about to run it's callee or a callback
1040            //
1041            // At (3), we must be careful because the "current" thread is *not* [componentIdx 1, task 1] which
1042            // is about to try to run it's callback.
1043            //
1044            // This is complicated because when two tasks run at the same time, we have to ensure that the component
1045            // is not exclusively locked by one task. This generally happens @ task.enter(), but an interleaving
1046            // of events in which this check happens, then *another* task attempts to exclusively lock could happen.
1047            //
1048            // In the future, this mechanism may be replaced with a simple event loop that necessarily executes
1049            // all pending work serially, with this intrinsic becoming simply queueing work onto that event loop.
1050            //
1051            Self::WithGlobalCurrentTaskMetaFnAsync => {
1052                let debug_log_fn = args.require_intrinsic(Intrinsic::DebugLog);
1053                let with_global_current_task_meta_async_fn =
1054                    args.require_intrinsic(Self::WithGlobalCurrentTaskMetaFnAsync);
1055                let global_current_task_meta_obj =
1056                    args.require_intrinsic(Self::GlobalCurrentTaskMeta);
1057
1058                output.push_str(&format!(
1059                    r#"
1060                      async function {with_global_current_task_meta_async_fn}(args) {{
1061                          {debug_log_fn}('[{with_global_current_task_meta_async_fn}()] args', args);
1062                          if (!args) {{ throw new TypeError('args missing'); }}
1063                          if (args.taskID === undefined) {{ throw new TypeError('missing task ID'); }}
1064                          if (args.componentIdx === undefined) {{ throw new TypeError('missing component idx'); }}
1065                          if (!args.fn) {{ throw new TypeError('missing fn'); }}
1066
1067                          const {{ taskID, componentIdx, fn }} = args;
1068
1069                          try {{
1070                              {global_current_task_meta_obj}[componentIdx] = {{ taskID, componentIdx }};
1071                              return await fn();
1072                          }} catch (err) {{
1073                              {debug_log_fn}("error while executing async callee/callback", {{
1074                                  ...args,
1075                                  err,
1076                              }});
1077                              throw err;
1078                          }} finally {{
1079                              {global_current_task_meta_obj}[componentIdx] = null;
1080                          }}
1081                      }}
1082                    "#,
1083                ));
1084            }
1085
1086            Self::ClearGlobalCurrentTaskMetaFn => {
1087                let debug_log_fn = args.require_intrinsic(Intrinsic::DebugLog);
1088                let clear_global_current_task_meta_fn =
1089                    args.require_intrinsic(Self::ClearGlobalCurrentTaskMetaFn);
1090                let global_current_task_meta_obj =
1091                    args.require_intrinsic(Self::GlobalCurrentTaskMeta);
1092
1093                output.push_str(&format!(
1094                    r#"
1095                      async function {clear_global_current_task_meta_fn}(args) {{
1096                          {debug_log_fn}('[{clear_global_current_task_meta_fn}()] args', args);
1097                          if (!args) {{ throw new TypeError('args missing'); }}
1098                          if (args.taskID === undefined) {{ throw new TypeError('missing task ID'); }}
1099                          if (args.componentIdx === undefined) {{ throw new TypeError('missing component idx'); }}
1100                          const {{ taskID, componentIdx }} = args;
1101
1102                          const meta = {global_current_task_meta_obj}[componentIdx];
1103                          if (!meta) {{ throw new Error(`missing current task meta for component idx [${{componentIdx}}]`); }}
1104
1105                          if (meta.taskID !== taskID) {{
1106                              throw new Error(`task ID [${{meta.taskID}}] != requested ID [${{taskID}}]`);
1107                          }}
1108                          if (meta.componentIdx !== componentIdx) {{
1109                              throw new Error(`component idx [${{meta.componentIdx}}] != requested idx [${{componentIdx}}]`);
1110                          }}
1111
1112                          {global_current_task_meta_obj}[componentIdx] = null;
1113                      }}
1114                    "#,
1115                ));
1116            }
1117
1118            // Under JSPI a wasm stack suspends inside a task's callback
1119            // slice; other tasks then set the per-component current-task
1120            // register. Restoring the captured entry when the awaited
1121            // import settles is the last JS to run before the suspended
1122            // stack resumes, so the resumed continuation's context.get /
1123            // context.set (and task-exit bookkeeping) address the task
1124            // that is actually executing.
1125            Self::SuspendingImportWrapperFn => {
1126                let suspending_import_wrapper_fn =
1127                    args.require_intrinsic(Self::SuspendingImportWrapperFn);
1128                let global_current_task_meta_obj =
1129                    args.require_intrinsic(Self::GlobalCurrentTaskMeta);
1130
1131                output.push_str(&format!(
1132                    r#"
1133                      function {suspending_import_wrapper_fn}(componentIdx, fn) {{
1134                          return async function (...args) {{
1135                              const saved = {global_current_task_meta_obj}[componentIdx] ?? null;
1136                              try {{
1137                                  return await fn.apply(null, args);
1138                              }} finally {{
1139                                  {global_current_task_meta_obj}[componentIdx] = saved;
1140                              }}
1141                          }};
1142                      }}
1143                    "#,
1144                ));
1145            }
1146
1147            // TODO(feat): customizable stream classes
1148            Intrinsic::PlatformReadableStreamClass => {
1149                let name = self.name();
1150                uwriteln!(
1151                    output,
1152                    r#"
1153                        if (!ReadableStream) {{
1154                            throw new Error('builtin stream class [ReadableStream] is not available');
1155                        }}
1156                        const {name} = ReadableStream;
1157                    "#
1158                );
1159            }
1160        }
1161    }
1162}
1163
1164#[cfg(test)]
1165mod tests {
1166    use super::*;
1167
1168    fn render(initial: impl IntoIterator<Item = Intrinsic>) -> (Source, BTreeSet<Intrinsic>) {
1169        let mut intrinsics = initial.into_iter().collect();
1170        let opts = TranspileOpts::default();
1171        let source = render_intrinsics(
1172            RenderIntrinsicsArgs::builder()
1173                .intrinsics(&mut intrinsics)
1174                .transpile_opts(&opts)
1175                .build(),
1176        );
1177        (source, intrinsics)
1178    }
1179
1180    #[test]
1181    fn renders_only_requested_and_discovered_intrinsics() {
1182        let (source, intrinsics) = render([Intrinsic::CoinFlip]);
1183
1184        assert_eq!(intrinsics, BTreeSet::from([Intrinsic::CoinFlip]));
1185        assert!(source.contains("Math.random()"));
1186        assert!(!source.contains("_debugLog"));
1187        assert!(!source.contains("class RepTable"));
1188    }
1189
1190    #[test]
1191    fn component_async_state_does_not_pull_in_create_stream_or_create_future() {
1192        let state = Intrinsic::Component(ComponentIntrinsic::ComponentAsyncStateClass);
1193        let create_stream = Intrinsic::AsyncStream(AsyncStreamIntrinsic::CreateStream);
1194        let get_stream_end = Intrinsic::AsyncStream(AsyncStreamIntrinsic::GetStreamEnd);
1195        let create_future = Intrinsic::AsyncFuture(AsyncFutureIntrinsic::CreateFuture);
1196        let get_future_end = Intrinsic::AsyncFuture(AsyncFutureIntrinsic::GetFutureEnd);
1197        let (source, intrinsics) = render([state]);
1198
1199        assert!(!intrinsics.contains(&create_stream));
1200        assert!(!intrinsics.contains(&get_stream_end));
1201        assert!(!intrinsics.contains(&create_future));
1202        assert!(!intrinsics.contains(&get_future_end));
1203        assert!(!source.contains("function createStream(cstate, args)"));
1204        assert!(!source.contains("function createFuture(cstate, args)"));
1205
1206        let (source, _) = render([create_stream]);
1207        assert!(source.contains("function createStream(cstate, args)"));
1208        assert!(!source.contains("function getStreamEnd(args)"));
1209        assert!(!source.contains("function createFuture(cstate, args)"));
1210
1211        let (source, _) = render([create_future]);
1212        assert!(source.contains("function createFuture(cstate, args)"));
1213        assert!(!source.contains("function getFutureEnd(args)"));
1214        assert!(!source.contains("function createStream(cstate, args)"));
1215
1216        let (_, intrinsics) = render([Intrinsic::Lift(LiftIntrinsic::LiftFlatStream)]);
1217        assert!(intrinsics.contains(&get_stream_end));
1218        assert!(!intrinsics.contains(&create_stream));
1219
1220        let (_, intrinsics) = render([Intrinsic::Lift(LiftIntrinsic::LiftFlatFuture)]);
1221        assert!(intrinsics.contains(&get_future_end));
1222        assert!(!intrinsics.contains(&create_future));
1223    }
1224
1225    #[test]
1226    fn stream_and_future_helpers_are_individual_intrinsics() {
1227        let helpers = [
1228            (
1229                Intrinsic::AsyncStream(AsyncStreamIntrinsic::CreateStream),
1230                "createStream",
1231            ),
1232            (
1233                Intrinsic::AsyncStream(AsyncStreamIntrinsic::GetStreamEnd),
1234                "getStreamEnd",
1235            ),
1236            (
1237                Intrinsic::AsyncStream(AsyncStreamIntrinsic::AddStreamEndToTable),
1238                "addStreamEndToTable",
1239            ),
1240            (
1241                Intrinsic::AsyncStream(AsyncStreamIntrinsic::DeleteStreamEnd),
1242                "deleteStreamEnd",
1243            ),
1244            (
1245                Intrinsic::AsyncStream(AsyncStreamIntrinsic::RemoveStreamEndFromTable),
1246                "removeStreamEndFromTable",
1247            ),
1248            (
1249                Intrinsic::AsyncFuture(AsyncFutureIntrinsic::CreateFuture),
1250                "createFuture",
1251            ),
1252            (
1253                Intrinsic::AsyncFuture(AsyncFutureIntrinsic::GetFutureEnd),
1254                "getFutureEnd",
1255            ),
1256            (
1257                Intrinsic::AsyncFuture(AsyncFutureIntrinsic::AddFutureEndToTable),
1258                "addFutureEndToTable",
1259            ),
1260            (
1261                Intrinsic::AsyncFuture(AsyncFutureIntrinsic::RemoveFutureEndFromTable),
1262                "removeFutureEndFromTable",
1263            ),
1264        ];
1265
1266        for &(intrinsic, name) in &helpers {
1267            let (source, intrinsics) = render([intrinsic]);
1268            assert!(intrinsics.contains(&intrinsic));
1269
1270            for &(_, other_name) in &helpers {
1271                assert_eq!(
1272                    source.contains(&format!("function {other_name}(")),
1273                    name == other_name,
1274                    "rendering {name} unexpectedly changed whether {other_name} was emitted",
1275                );
1276            }
1277        }
1278    }
1279
1280    #[test]
1281    fn discovers_transitive_dependencies_in_dependency_order() {
1282        let transfer = Intrinsic::Resource(ResourceIntrinsic::ResourceTransferBorrow);
1283        let table_flag = Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag);
1284        let table_get = Intrinsic::Resource(ResourceIntrinsic::ResourceTableGet);
1285        let table_remove = Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove);
1286        let (source, intrinsics) = render([transfer]);
1287
1288        for dependency in [table_flag, table_get, table_remove] {
1289            assert!(intrinsics.contains(&dependency));
1290        }
1291
1292        let flag_position = source.find("const T_FLAG").unwrap();
1293        let get_position = source.find("function rscTableGet").unwrap();
1294        let remove_position = source.find("function rscTableRemove").unwrap();
1295        let transfer_position = source.find("function resourceTransferBorrow").unwrap();
1296        assert!(flag_position < get_position);
1297        assert!(flag_position < remove_position);
1298        assert!(get_position < transfer_position);
1299        assert!(remove_position < transfer_position);
1300    }
1301
1302    #[test]
1303    fn self_dependencies_are_cycle_safe() {
1304        let current_tasks = Intrinsic::AsyncTask(AsyncTaskIntrinsic::GlobalAsyncCurrentTaskMap);
1305        let (source, intrinsics) = render([current_tasks]);
1306
1307        assert_eq!(intrinsics, BTreeSet::from([current_tasks]));
1308        assert_eq!(
1309            source.matches("const ASYNC_TASKS_BY_COMPONENT_IDX").count(),
1310            1
1311        );
1312    }
1313
1314    #[test]
1315    fn resource_transfer_borrow_checks_source_handle() {
1316        let mut intrinsics = BTreeSet::from([Intrinsic::Resource(
1317            ResourceIntrinsic::ResourceTransferBorrow,
1318        )]);
1319        let opts = TranspileOpts::default();
1320        let source = render_intrinsics(
1321            RenderIntrinsicsArgs::builder()
1322                .intrinsics(&mut intrinsics)
1323                .transpile_opts(&opts)
1324                .build(),
1325        );
1326
1327        assert!(source.contains("function rscTableGet(table, handle)"));
1328        assert!(source.contains("function rscTableRemove(table, handle)"));
1329        assert!(source.contains("const { rep, own } = rscTableGet(fromTable, handle);"));
1330        assert!(source.contains("if (!own) rscTableRemove(fromTable, handle);"));
1331    }
1332
1333    /// Future read/write trampoline code references the future end classes
1334    /// (`instanceof FutureReadableEnd`, `FutureEnd.CopyState`), so the classes
1335    /// must be emitted even when `FutureNew` is absent (see #1898).
1336    #[test]
1337    fn future_read_write_emit_future_end_classes() {
1338        for (op, end_class) in [
1339            (AsyncFutureIntrinsic::FutureRead, "class FutureReadableEnd"),
1340            (AsyncFutureIntrinsic::FutureWrite, "class FutureWritableEnd"),
1341        ] {
1342            let mut intrinsics = BTreeSet::from([Intrinsic::AsyncFuture(op)]);
1343            let opts = TranspileOpts::default();
1344            let source = render_intrinsics(
1345                RenderIntrinsicsArgs::builder()
1346                    .intrinsics(&mut intrinsics)
1347                    .transpile_opts(&opts)
1348                    .build(),
1349            );
1350
1351            assert!(source.contains(end_class), "missing {end_class}");
1352            assert!(source.contains("class FutureEnd"), "missing FutureEnd");
1353        }
1354    }
1355
1356    #[test]
1357    fn flat_flags_bigint_representation_is_opt_in() {
1358        fn render(flags_as_bigint: bool) -> Source {
1359            let mut intrinsics = BTreeSet::from([
1360                Intrinsic::Lift(LiftIntrinsic::LiftFlatFlags),
1361                Intrinsic::Lower(LowerIntrinsic::LowerFlatFlags),
1362            ]);
1363            let opts = TranspileOpts::builder()
1364                .name("test".into())
1365                .flags_as_bigint(flags_as_bigint)
1366                .build();
1367            render_intrinsics(
1368                RenderIntrinsicsArgs::builder()
1369                    .intrinsics(&mut intrinsics)
1370                    .transpile_opts(&opts)
1371                    .build(),
1372            )
1373        }
1374
1375        let default_source = render(false);
1376        assert!(default_source.contains("val[name] = (bits & 1) === 1;"));
1377        assert!(default_source.contains("const flagObj = ctx.vals[0];"));
1378        assert!(!default_source.contains("val = BigInt(bits >>> 0);"));
1379
1380        let bigint_source = render(true);
1381        assert!(bigint_source.contains("val = BigInt(bits >>> 0);"));
1382        assert!(bigint_source.contains("typeof bigintFlags !== 'bigint'"));
1383        assert!(!bigint_source.contains("const flagObj = ctx.vals[0];"));
1384    }
1385}
1386
1387/// Profile for determinism to be used by async implementation
1388#[derive(Debug, Default, PartialEq, Eq)]
1389pub enum AsyncDeterminismProfile {
1390    /// Allow random ordering non-determinism
1391    #[default]
1392    Random,
1393
1394    /// Require determinism
1395    #[allow(unused)]
1396    Deterministic,
1397}
1398
1399impl std::fmt::Display for AsyncDeterminismProfile {
1400    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1401        write!(
1402            f,
1403            "{}",
1404            match self {
1405                Self::Deterministic => "deterministic",
1406                Self::Random => "random",
1407            }
1408        )
1409    }
1410}
1411
1412/// Arguments to `render_intrinsics`
1413#[derive(bon::Builder)]
1414#[non_exhaustive]
1415pub struct RenderIntrinsicsArgs<'a> {
1416    /// List of intrinsics being built for use
1417    pub(crate) intrinsics: &'a mut BTreeSet<Intrinsic>,
1418    /// Whether instantiation has occurred
1419    #[builder(default)]
1420    pub(crate) instantiation_occurred: bool,
1421    /// The kind of determinism to use
1422    #[builder(default)]
1423    pub(crate) determinism_profile: AsyncDeterminismProfile,
1424    /// Options provided when performing transpilation
1425    pub(crate) transpile_opts: &'a TranspileOpts,
1426    /// Intrinsic dependencies discovered while rendering one intrinsic
1427    #[builder(default)]
1428    discovered_intrinsics: Mutex<BTreeSet<Intrinsic>>,
1429}
1430
1431impl RenderIntrinsicsArgs<'_> {
1432    /// Retrieve an intrinsic's name and register it as a dependency of the
1433    /// intrinsic currently being rendered.
1434    pub fn require_intrinsic(&self, intrinsic: impl Into<Intrinsic>) -> &'static str {
1435        let intrinsic = intrinsic.into();
1436        self.discovered_intrinsics
1437            .lock()
1438            .expect("intrinsic dependency collector lock should not be poisoned")
1439            .insert(intrinsic);
1440        intrinsic.name()
1441    }
1442
1443    fn take_discovered_intrinsics(&self) -> BTreeSet<Intrinsic> {
1444        std::mem::take(
1445            &mut *self
1446                .discovered_intrinsics
1447                .lock()
1448                .expect("intrinsic dependency collector lock should not be poisoned"),
1449        )
1450    }
1451}
1452
1453/// Emits the intrinsic `i` to this file and then returns the name of the
1454/// intrinsic.
1455pub fn render_intrinsics(mut args: RenderIntrinsicsArgs) -> Source {
1456    render_intrinsics_discovered(&mut args)
1457}
1458
1459fn render_intrinsics_discovered(args: &mut RenderIntrinsicsArgs<'_>) -> Source {
1460    let mut pending = args.intrinsics.clone();
1461    let mut rendered = BTreeMap::new();
1462    let mut dependencies = BTreeMap::new();
1463
1464    while let Some(intrinsic) = pending.pop_first() {
1465        if rendered.contains_key(&intrinsic) {
1466            continue;
1467        }
1468
1469        debug_assert!(args.take_discovered_intrinsics().is_empty());
1470        let mut source = Source::default();
1471        intrinsic.render(&mut source, args);
1472        let discovered = args.take_discovered_intrinsics();
1473        for dependency in &discovered {
1474            if !rendered.contains_key(dependency) {
1475                pending.insert(*dependency);
1476            }
1477            args.intrinsics.insert(*dependency);
1478        }
1479        dependencies.insert(intrinsic, discovered);
1480        rendered.insert(intrinsic, source);
1481    }
1482
1483    let mut output = Source::default();
1484    if args
1485        .intrinsics
1486        .contains(&Intrinsic::Conversion(ConversionIntrinsic::F32ToI32))
1487        || args
1488            .intrinsics
1489            .contains(&Intrinsic::Conversion(ConversionIntrinsic::I32ToF32))
1490    {
1491        output.push_str(
1492            "
1493            const i32ToF32I = new Int32Array(1);
1494            const i32ToF32F = new Float32Array(i32ToF32I.buffer);
1495        ",
1496        );
1497    }
1498
1499    if args
1500        .intrinsics
1501        .contains(&Intrinsic::Conversion(ConversionIntrinsic::F64ToI64))
1502        || args
1503            .intrinsics
1504            .contains(&Intrinsic::Conversion(ConversionIntrinsic::I64ToF64))
1505    {
1506        output.push_str(
1507            "
1508            const i64ToF64I = new BigInt64Array(1);
1509            const i64ToF64F = new Float64Array(i64ToF64I.buffer);
1510        ",
1511        );
1512    }
1513
1514    let mut visiting = BTreeSet::new();
1515    let mut emitted = BTreeSet::new();
1516    for intrinsic in args.intrinsics.iter().copied() {
1517        emit_intrinsic(
1518            intrinsic,
1519            &dependencies,
1520            &rendered,
1521            &mut visiting,
1522            &mut emitted,
1523            &mut output,
1524        );
1525    }
1526    output
1527}
1528
1529fn emit_intrinsic(
1530    intrinsic: Intrinsic,
1531    dependencies: &BTreeMap<Intrinsic, BTreeSet<Intrinsic>>,
1532    rendered: &BTreeMap<Intrinsic, Source>,
1533    visiting: &mut BTreeSet<Intrinsic>,
1534    emitted: &mut BTreeSet<Intrinsic>,
1535    output: &mut Source,
1536) {
1537    if emitted.contains(&intrinsic) || !visiting.insert(intrinsic) {
1538        return;
1539    }
1540
1541    if let Some(intrinsic_dependencies) = dependencies.get(&intrinsic) {
1542        for dependency in intrinsic_dependencies {
1543            emit_intrinsic(
1544                *dependency,
1545                dependencies,
1546                rendered,
1547                visiting,
1548                emitted,
1549                output,
1550            );
1551        }
1552    }
1553
1554    visiting.remove(&intrinsic);
1555    if emitted.insert(intrinsic) {
1556        output.push_str(
1557            rendered
1558                .get(&intrinsic)
1559                .expect("intrinsic should have been rendered"),
1560        );
1561    }
1562}
1563
1564impl Intrinsic {
1565    pub fn get_global_names() -> impl IntoIterator<Item = &'static str> {
1566        JsHelperIntrinsic::get_global_names()
1567            .into_iter()
1568            .chain(vec![
1569                // Intrinsic list exactly as below
1570                "base64Compile",
1571                "clampGuest",
1572                "ComponentError",
1573                "WebAssemblyRuntimeError",
1574                "fetchCompile",
1575                "finalizationRegistryCreate",
1576                "getErrorPayload",
1577                "HANDLE_TABLES",
1578                "hasOwnProperty",
1579                "imports",
1580                "instantiateCore",
1581                "isLE",
1582                "scopeId",
1583                "symbolCabiDispose",
1584                "symbolCabiLower",
1585                "symbolDispose",
1586                "symbolAsyncIterator",
1587                "symbolIterator",
1588                "symbolRscHandle",
1589                "symbolRscRep",
1590                "T_FLAG",
1591                "throwInvalidBool",
1592                "throwUninitialized",
1593                // JS Globals / non intrinsic names
1594                "ArrayBuffer",
1595                "BigInt",
1596                "BigInt64Array",
1597                "DataView",
1598                "dv",
1599                "emptyFunc",
1600                "Error",
1601                "fetch",
1602                "Float32Array",
1603                "Float64Array",
1604                "Int32Array",
1605                "Object",
1606                "process",
1607                "String",
1608                "TextDecoder",
1609                "TextEncoder",
1610                "TypeError",
1611                "Uint16Array",
1612                "Uint8Array",
1613                "URL",
1614                "WebAssembly",
1615                "GlobalComponentMemories",
1616            ])
1617    }
1618
1619    pub fn name(&self) -> &'static str {
1620        match self {
1621            Intrinsic::JsHelper(i) => i.name(),
1622            Intrinsic::Conversion(i) => i.name(),
1623            Intrinsic::WebIdl(i) => i.name(),
1624            Intrinsic::String(i) => i.name(),
1625            Intrinsic::ErrCtx(i) => i.name(),
1626            Intrinsic::AsyncTask(i) => i.name(),
1627            Intrinsic::Waitable(i) => i.name(),
1628            Intrinsic::Resource(i) => i.name(),
1629            Intrinsic::Lift(i) => i.name(),
1630            Intrinsic::Lower(i) => i.name(),
1631            Intrinsic::AsyncStream(i) => i.name(),
1632            Intrinsic::AsyncFuture(i) => i.name(),
1633            Intrinsic::Component(i) => i.name(),
1634            Intrinsic::Host(i) => i.name(),
1635
1636            Intrinsic::Base64Compile => "base64Compile",
1637            Intrinsic::ClampGuest => "clampGuest",
1638            Intrinsic::ComponentError => "ComponentError",
1639            Intrinsic::WebAssemblyRuntimeError => "WebAssemblyRuntimeError",
1640            Intrinsic::FetchCompile => "fetchCompile",
1641            Intrinsic::FinalizationRegistryCreate => "finalizationRegistryCreate",
1642            Intrinsic::GetErrorPayload => "getErrorPayload",
1643            Intrinsic::GetErrorPayloadString => "getErrorPayloadString",
1644            Intrinsic::HandleTables => "HANDLE_TABLES",
1645            Intrinsic::HasOwnProperty => "hasOwnProperty",
1646            Intrinsic::InstantiateCore => "instantiateCore",
1647            Intrinsic::IsLE => "isLE",
1648            Intrinsic::ScopeId => "SCOPE_ID",
1649
1650            Intrinsic::SymbolCabiDispose => "symbolCabiDispose",
1651            Intrinsic::SymbolCabiLower => "symbolCabiLower",
1652            Intrinsic::SymbolDispose => "symbolDispose",
1653            Intrinsic::SymbolAsyncIterator => "symbolAsyncIterator",
1654            Intrinsic::SymbolIterator => "symbolIterator",
1655            Intrinsic::SymbolResourceHandle => "symbolRscHandle",
1656            Intrinsic::SymbolResourceRep => "symbolRscRep",
1657
1658            Intrinsic::ThrowInvalidBool => "throwInvalidBool",
1659            Intrinsic::ThrowUninitialized => "throwUninitialized",
1660
1661            // Debugging
1662            Intrinsic::DebugLog => "_debugLog",
1663            Intrinsic::PromiseWithResolversPonyfill => "promiseWithResolvers",
1664
1665            // Types
1666            Intrinsic::ConstantI32Min => "I32_MIN",
1667            Intrinsic::ConstantI32Max => "I32_MAX",
1668            Intrinsic::TypeCheckValidI32 => "_typeCheckValidI32",
1669            Intrinsic::TypeCheckAsyncFn => "_typeCheckAsyncFn",
1670            Intrinsic::AsyncFunctionCtor => "ASYNC_FN_CTOR",
1671
1672            // Streams
1673            Intrinsic::PlatformReadableStreamClass => "_PlatformReadableStream",
1674
1675            // Async
1676            Intrinsic::GlobalAsyncDeterminism => "ASYNC_DETERMINISM",
1677            Intrinsic::CoinFlip => "_coinFlip",
1678
1679            // Global current task tracking machinery
1680            Self::GlobalCurrentTaskMeta => "CURRENT_TASK_META",
1681            Self::GetGlobalCurrentTaskMetaFn => "_getGlobalCurrentTaskMeta",
1682            Self::SetGlobalCurrentTaskMetaFn => "_setGlobalCurrentTaskMeta",
1683            Self::WithGlobalCurrentTaskMetaFn => "_withGlobalCurrentTaskMeta",
1684            Self::WithGlobalCurrentTaskMetaFnAsync => "_withGlobalCurrentTaskMetaAsync",
1685            Self::ClearGlobalCurrentTaskMetaFn => "_clearCurrentTask",
1686            Self::SuspendingImportWrapperFn => "_suspendingImport",
1687
1688            // Iteratively saved metadata
1689            Intrinsic::GlobalComponentMemoryMap => "GLOBAL_COMPONENT_MEMORY_MAP",
1690            Intrinsic::RegisterGlobalMemoryForComponent => "registerGlobalMemoryForComponent",
1691            Intrinsic::LookupMemoriesForComponent => "lookupMemoriesForComponent",
1692
1693            // Data structures
1694            Intrinsic::RepTableClass => "RepTable",
1695
1696            // Buffers for managed/synchronized writing to/from component memory
1697            Intrinsic::ManagedBufferClass => "ManagedBuffer",
1698            Intrinsic::BufferManagerClass => "BufferManager",
1699            Intrinsic::GlobalBufferManager => "BUFFER_MGR",
1700
1701            // Helpers for working with async state
1702            Intrinsic::AsyncEventCodeEnum => "ASYNC_EVENT_CODE",
1703        }
1704    }
1705}