js_component_bindgen/intrinsics/
mod.rs

1//! Intrinsics used from JS
2
3use std::collections::BTreeSet;
4use std::fmt::Write;
5
6use crate::source::Source;
7use crate::uwrite;
8
9pub(crate) mod conversion;
10use conversion::ConversionIntrinsic;
11
12pub(crate) mod js_helper;
13use js_helper::JsHelperIntrinsic;
14
15pub(crate) mod webidl;
16use webidl::WebIdlIntrinsic;
17
18pub(crate) mod string;
19use string::StringIntrinsic;
20
21pub(crate) mod resource;
22use resource::ResourceIntrinsic;
23
24pub(crate) mod lift;
25use lift::LiftIntrinsic;
26
27pub(crate) mod component;
28use component::ComponentIntrinsic;
29
30pub(crate) mod p3;
31use p3::async_future::AsyncFutureIntrinsic;
32use p3::async_stream::AsyncStreamIntrinsic;
33use p3::async_task::AsyncTaskIntrinsic;
34use p3::error_context::ErrCtxIntrinsic;
35use p3::waitable::WaitableIntrinsic;
36
37/// List of all intrinsics that are used by these
38///
39/// These intrinsics refer to JS code that is included in order to make
40/// transpiled WebAssembly components and their imports/exports functional
41/// in the relevant JS context.
42#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
43pub enum Intrinsic {
44    JsHelper(JsHelperIntrinsic),
45    WebIdl(WebIdlIntrinsic),
46    Conversion(ConversionIntrinsic),
47    String(StringIntrinsic),
48    Resource(ResourceIntrinsic),
49    ErrCtx(ErrCtxIntrinsic),
50    AsyncTask(AsyncTaskIntrinsic),
51    Waitable(WaitableIntrinsic),
52    Lift(LiftIntrinsic),
53    AsyncStream(AsyncStreamIntrinsic),
54    AsyncFuture(AsyncFutureIntrinsic),
55    Component(ComponentIntrinsic),
56
57    // Polyfills
58    PromiseWithResolversPolyfill,
59
60    /// Enable debug logging
61    DebugLog,
62
63    /// Global setting for determinism (used in async)
64    GlobalAsyncDeterminism,
65
66    /// Randomly produce a boolean true/false
67    CoinFlip,
68
69    // Basic type helpers
70    ConstantI32Max,
71    ConstantI32Min,
72    TypeCheckValidI32,
73
74    Base64Compile,
75    ClampGuest,
76    FetchCompile,
77
78    // Globals
79    SymbolCabiDispose,
80    SymbolCabiLower,
81    SymbolResourceHandle,
82    SymbolResourceRep,
83    SymbolDispose,
84    ScopeId,
85    DefinedResourceTables,
86    HandleTables,
87
88    // Global Initializers
89    FinalizationRegistryCreate,
90
91    // Global classes
92    ComponentError,
93
94    // WASI object helpers
95    GetErrorPayload,
96    GetErrorPayloadString,
97
98    /// Class that manages (and synchronizes) writes to managed buffers
99    ManagedBufferClass,
100
101    /// Buffer manager that is used to synchronize component writes
102    BufferManagerClass,
103
104    /// Global for an instantiated buffer manager singleton
105    GlobalBufferManager,
106
107    /// Reusable table structure for holding canonical ABI objects by their representation/identifier of (e.g. resources, waitables, etc)
108    ///
109    /// Representations of objects stored in one of these tables is a u32 (0 is expected to be an invalid index).
110    RepTableClass,
111
112    /// Class that wraps `Promise`s and other things that can be awaited so that we can
113    /// keep track of whether resolutions have happened
114    AwaitableClass,
115
116    /// Event codes used for async, as a JS enum
117    AsyncEventCodeEnum,
118
119    /// Write an async event (e.g. result of waitable-set.wait) to linear memory
120    WriteAsyncEventToMemory,
121
122    // JS helper functions
123    IsLE,
124    ThrowInvalidBool,
125    ThrowUninitialized,
126    HasOwnProperty,
127    InstantiateCore,
128
129    /// JS helper function for check whether a given type is borrowed
130    ///
131    /// Generally the only kind of type that can be borrowed is a resource
132    /// handle, so this helper checks for that.
133    IsBorrowedType,
134}
135
136/// Profile for determinism to be used by async implementation
137#[derive(Debug, Default, PartialEq, Eq)]
138pub(crate) enum AsyncDeterminismProfile {
139    /// Allow random ordering non-determinism
140    #[default]
141    Random,
142
143    /// Require determinism
144    #[allow(unused)]
145    Deterministic,
146}
147
148impl std::fmt::Display for AsyncDeterminismProfile {
149    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150        write!(
151            f,
152            "{}",
153            match self {
154                Self::Deterministic => "deterministic",
155                Self::Random => "random",
156            }
157        )
158    }
159}
160
161/// Arguments to `render_intrinsics`
162pub struct RenderIntrinsicsArgs<'a> {
163    /// List of intrinsics being built for use
164    pub(crate) intrinsics: &'a mut BTreeSet<Intrinsic>,
165    /// Whether to use NodeJS compat
166    pub(crate) no_nodejs_compat: bool,
167    /// Whether instantiation has occurred
168    pub(crate) instantiation: bool,
169    /// The kind of determinism to use
170    pub(crate) determinism: AsyncDeterminismProfile,
171}
172
173/// Emits the intrinsic `i` to this file and then returns the name of the
174/// intrinsic.
175pub fn render_intrinsics(args: RenderIntrinsicsArgs) -> Source {
176    let mut output = Source::default();
177
178    // Intrinsics that should just always be present
179    args.intrinsics.insert(Intrinsic::DebugLog);
180    args.intrinsics.insert(Intrinsic::GlobalAsyncDeterminism);
181    args.intrinsics.insert(Intrinsic::CoinFlip);
182    args.intrinsics.insert(Intrinsic::ConstantI32Min);
183    args.intrinsics.insert(Intrinsic::ConstantI32Max);
184    args.intrinsics.insert(Intrinsic::TypeCheckValidI32);
185    args.intrinsics.insert(Intrinsic::AsyncTask(
186        AsyncTaskIntrinsic::GlobalAsyncCurrentTaskIds,
187    ));
188    args.intrinsics.insert(Intrinsic::AsyncTask(
189        AsyncTaskIntrinsic::GlobalAsyncCurrentComponentIdxs,
190    ));
191    args.intrinsics.insert(Intrinsic::AsyncTask(
192        AsyncTaskIntrinsic::UnpackCallbackResult,
193    ));
194    args.intrinsics
195        .insert(Intrinsic::PromiseWithResolversPolyfill);
196
197    // Handle intrinsic "dependence"
198    if args.intrinsics.contains(&Intrinsic::GetErrorPayload)
199        || args.intrinsics.contains(&Intrinsic::GetErrorPayloadString)
200    {
201        args.intrinsics.insert(Intrinsic::HasOwnProperty);
202    }
203    if args
204        .intrinsics
205        .contains(&Intrinsic::String(StringIntrinsic::Utf16Encode))
206    {
207        args.intrinsics.insert(Intrinsic::IsLE);
208    }
209
210    if args
211        .intrinsics
212        .contains(&Intrinsic::Conversion(ConversionIntrinsic::F32ToI32))
213        || args
214            .intrinsics
215            .contains(&Intrinsic::Conversion(ConversionIntrinsic::I32ToF32))
216    {
217        output.push_str(
218            "
219            const i32ToF32I = new Int32Array(1);
220            const i32ToF32F = new Float32Array(i32ToF32I.buffer);
221        ",
222        );
223    }
224
225    if args
226        .intrinsics
227        .contains(&Intrinsic::Conversion(ConversionIntrinsic::F64ToI64))
228        || args
229            .intrinsics
230            .contains(&Intrinsic::Conversion(ConversionIntrinsic::I64ToF64))
231    {
232        output.push_str(
233            "
234            const i64ToF64I = new BigInt64Array(1);
235            const i64ToF64F = new Float64Array(i64ToF64I.buffer);
236        ",
237        );
238    }
239
240    if args.intrinsics.contains(&Intrinsic::Resource(
241        ResourceIntrinsic::ResourceTransferBorrow,
242    )) || args.intrinsics.contains(&Intrinsic::Resource(
243        ResourceIntrinsic::ResourceTransferBorrowValidLifting,
244    )) {
245        args.intrinsics.insert(Intrinsic::Resource(
246            ResourceIntrinsic::ResourceTableCreateBorrow,
247        ));
248    }
249
250    // Attempting to perform a debug message hoist will require string encoding to memory
251    if args
252        .intrinsics
253        .contains(&Intrinsic::ErrCtx(ErrCtxIntrinsic::DebugMessage))
254    {
255        args.intrinsics.extend([
256            &Intrinsic::String(StringIntrinsic::Utf8Encode),
257            &Intrinsic::String(StringIntrinsic::Utf16Encode),
258            &Intrinsic::ErrCtx(ErrCtxIntrinsic::GetLocalTable),
259        ]);
260    }
261    if args
262        .intrinsics
263        .contains(&Intrinsic::ErrCtx(ErrCtxIntrinsic::New))
264    {
265        args.intrinsics.extend([
266            &Intrinsic::ErrCtx(ErrCtxIntrinsic::ComponentGlobalTable),
267            &Intrinsic::ErrCtx(ErrCtxIntrinsic::ReserveGlobalRep),
268            &Intrinsic::ErrCtx(ErrCtxIntrinsic::CreateLocalHandle),
269            &Intrinsic::ErrCtx(ErrCtxIntrinsic::GetLocalTable),
270        ]);
271    }
272
273    if args
274        .intrinsics
275        .contains(&Intrinsic::ErrCtx(ErrCtxIntrinsic::DebugMessage))
276    {
277        args.intrinsics.extend([
278            &Intrinsic::ErrCtx(ErrCtxIntrinsic::GlobalRefCountAdd),
279            &Intrinsic::ErrCtx(ErrCtxIntrinsic::Drop),
280            &Intrinsic::ErrCtx(ErrCtxIntrinsic::GetLocalTable),
281        ]);
282    }
283
284    if args
285        .intrinsics
286        .contains(&Intrinsic::AsyncTask(AsyncTaskIntrinsic::ContextGet))
287        || args
288            .intrinsics
289            .contains(&Intrinsic::AsyncTask(AsyncTaskIntrinsic::ContextSet))
290    {
291        args.intrinsics.extend([
292            &Intrinsic::AsyncTask(AsyncTaskIntrinsic::GlobalAsyncCurrentTaskMap),
293            &Intrinsic::AsyncTask(AsyncTaskIntrinsic::AsyncTaskClass),
294            &Intrinsic::AsyncEventCodeEnum,
295            &Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask),
296        ]);
297    }
298
299    if args
300        .intrinsics
301        .contains(&Intrinsic::Component(ComponentIntrinsic::BackpressureSet))
302    {
303        args.intrinsics.extend([&Intrinsic::Component(
304            ComponentIntrinsic::GetOrCreateAsyncState,
305        )]);
306    }
307
308    if args.intrinsics.contains(&Intrinsic::Component(
309        ComponentIntrinsic::GetOrCreateAsyncState,
310    )) {
311        args.intrinsics.extend([&Intrinsic::RepTableClass]);
312    }
313
314    if args
315        .intrinsics
316        .contains(&Intrinsic::AsyncTask(AsyncTaskIntrinsic::AsyncTaskClass))
317    {
318        args.intrinsics.extend([
319            &Intrinsic::Component(ComponentIntrinsic::GetOrCreateAsyncState),
320            &Intrinsic::Component(ComponentIntrinsic::GlobalAsyncStateMap),
321            &Intrinsic::RepTableClass,
322            &Intrinsic::AwaitableClass,
323        ]);
324    }
325
326    if args.intrinsics.contains(&Intrinsic::Component(
327        ComponentIntrinsic::GetOrCreateAsyncState,
328    )) {
329        args.intrinsics.extend([
330            &Intrinsic::Component(ComponentIntrinsic::ComponentAsyncStateClass),
331            &Intrinsic::Component(ComponentIntrinsic::GlobalAsyncStateMap),
332        ]);
333    }
334
335    if args
336        .intrinsics
337        .contains(&Intrinsic::AsyncTask(AsyncTaskIntrinsic::StartCurrentTask))
338        || args
339            .intrinsics
340            .contains(&Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask))
341        || args
342            .intrinsics
343            .contains(&Intrinsic::AsyncTask(AsyncTaskIntrinsic::EndCurrentTask))
344    {
345        args.intrinsics.extend([
346            &Intrinsic::AsyncTask(AsyncTaskIntrinsic::AsyncTaskClass),
347            &Intrinsic::AsyncTask(AsyncTaskIntrinsic::GlobalAsyncCurrentTaskMap),
348        ]);
349    }
350
351    // Render all provided intrinsics
352    for current_intrinsic in args.intrinsics.iter() {
353        match current_intrinsic {
354            Intrinsic::JsHelper(i) => i.render(&mut output),
355            Intrinsic::Conversion(i) => i.render(&mut output),
356            Intrinsic::String(i) => i.render(&mut output),
357            Intrinsic::ErrCtx(i) => i.render(&mut output),
358            Intrinsic::Resource(i) => i.render(&mut output),
359            Intrinsic::AsyncTask(i) => i.render(&mut output),
360            Intrinsic::Waitable(i) => i.render(&mut output, &args),
361            Intrinsic::Lift(i) => i.render(&mut output),
362            Intrinsic::AsyncStream(i) => i.render(&mut output),
363            Intrinsic::AsyncFuture(i) => i.render(&mut output),
364            Intrinsic::Component(i) => i.render(&mut output),
365
366            Intrinsic::GlobalAsyncDeterminism => {
367                output.push_str(&format!(
368                    "const {var_name} = '{determinism}';\n",
369                    var_name = current_intrinsic.name(),
370                    determinism = args.determinism,
371                ));
372            }
373
374            Intrinsic::CoinFlip => {
375                output.push_str(&format!(
376                    "const {var_name} = () => {{ return Math.random() > 0.5; }};\n",
377                    var_name = current_intrinsic.name(),
378                ));
379            }
380
381            Intrinsic::AwaitableClass => {
382                output.push_str(&format!("
383                    class {class_name} {{
384                        static _ID = 0n;
385
386                        #id;
387                        #promise;
388                        #resolved = false;
389
390                        constructor(promise) {{
391                            if (!promise) {{ throw new TypeError('Awaitable must have an interior promise'); }}
392                            if (!('then' in promise) || typeof promise.then !== 'function') {{
393                                throw new Error('missing/invalid promise');
394                            }}
395                            promise.then(() => this.#resolved  = true);
396                            this.#promise = promise;
397                            this.#id = ++{class_name}._ID;
398                        }}
399
400                        id() {{ return this.#id; }}
401
402                        resolved() {{ return this.#resolved; }}
403
404                        then() {{ return this.#promise.then(...arguments); }}
405                    }}
406                ",
407                class_name = current_intrinsic.name(),
408                ));
409            }
410
411            Intrinsic::ConstantI32Min => output.push_str(&format!(
412                "const {const_name} = -2_147_483_648;\n",
413                const_name = current_intrinsic.name()
414            )),
415            Intrinsic::ConstantI32Max => output.push_str(&format!(
416                "const {const_name} = 2_147_483_647;\n",
417                const_name = current_intrinsic.name()
418            )),
419            Intrinsic::TypeCheckValidI32 => {
420                let i32_const_min = Intrinsic::ConstantI32Min.name();
421                let i32_const_max = Intrinsic::ConstantI32Max.name();
422                output.push_str(&format!("const {fn_name} = (n) => typeof n === 'number' && n >= {i32_const_min} && n <= {i32_const_max};\n", fn_name = current_intrinsic.name()))
423            }
424
425            Intrinsic::Base64Compile => {
426                if !args.no_nodejs_compat {
427                    output.push_str("
428                    const base64Compile = str => WebAssembly.compile(typeof Buffer !== 'undefined' ? Buffer.from(str, 'base64') : Uint8Array.from(atob(str), b => b.charCodeAt(0)));
429                ")
430                } else {
431                    output.push_str("
432                    const base64Compile = str => WebAssembly.compile(Uint8Array.from(atob(str), b => b.charCodeAt(0)));
433                ")
434                }
435            }
436
437            Intrinsic::ClampGuest => output.push_str(
438                "
439                function clampGuest(i, min, max) {
440                    if (i < min || i > max) \
441                    throw new TypeError(`must be between ${min} and ${max}`);
442                    return i;
443                }
444            ",
445            ),
446
447            Intrinsic::ComponentError => output.push_str(
448                "
449                class ComponentError extends Error {
450                    constructor (value) {
451                        const enumerable = typeof value !== 'string';
452                        super(enumerable ? `${String(value)} (see error.payload)` : value);
453                        Object.defineProperty(this, 'payload', { value, enumerable });
454                    }
455                }
456            ",
457            ),
458
459            Intrinsic::DefinedResourceTables => {}
460
461            Intrinsic::FinalizationRegistryCreate => output.push_str(
462                "
463                function finalizationRegistryCreate (unregister) {
464                    if (typeof FinalizationRegistry === 'undefined') {
465                        return { unregister () {} };
466                    }
467                    return new FinalizationRegistry(unregister);
468                }
469            ",
470            ),
471
472            Intrinsic::FetchCompile => {
473                if !args.no_nodejs_compat {
474                    output.push_str("
475                    const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
476                    let _fs;
477                    async function fetchCompile (url) {
478                        if (isNode) {
479                            _fs = _fs || await import('node:fs/promises');
480                            return WebAssembly.compile(await _fs.readFile(url));
481                        }
482                        return fetch(url).then(WebAssembly.compileStreaming);
483                    }
484                ")
485                } else {
486                    output.push_str(
487                        "
488                    const fetchCompile = url => fetch(url).then(WebAssembly.compileStreaming);
489                ",
490                    )
491                }
492            }
493
494            Intrinsic::GetErrorPayload => {
495                let hop = Intrinsic::HasOwnProperty.name();
496                uwrite!(
497                    output,
498                    "
499                    function getErrorPayload(e) {{
500                        if (e && {hop}.call(e, 'payload')) return e.payload;
501                        if (e instanceof Error) throw e;
502                        return e;
503                    }}
504                "
505                )
506            }
507
508            Intrinsic::GetErrorPayloadString => {
509                let hop = Intrinsic::HasOwnProperty.name();
510                uwrite!(
511                    output,
512                    "
513                    function getErrorPayloadString(e) {{
514                        if (e && {hop}.call(e, 'payload')) return e.payload;
515                        if (e instanceof Error) return e.message;
516                        return e;
517                    }}
518                "
519                )
520            }
521
522            Intrinsic::WebIdl(w) => w.render(&mut output),
523
524            Intrinsic::HandleTables => output.push_str(
525                "
526                const handleTables = [];
527            ",
528            ),
529
530            Intrinsic::HasOwnProperty => output.push_str(
531                "
532                const hasOwnProperty = Object.prototype.hasOwnProperty;
533            ",
534            ),
535
536            Intrinsic::InstantiateCore => {
537                if !args.instantiation {
538                    output.push_str(
539                        "
540                    const instantiateCore = WebAssembly.instantiate;
541                ",
542                    )
543                }
544            }
545
546            Intrinsic::IsLE => output.push_str(
547                "
548                const isLE = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
549            ",
550            ),
551
552            Intrinsic::SymbolCabiDispose => output.push_str(
553                "
554                const symbolCabiDispose = Symbol.for('cabiDispose');
555            ",
556            ),
557
558            Intrinsic::SymbolCabiLower => output.push_str(
559                "
560                const symbolCabiLower = Symbol.for('cabiLower');
561            ",
562            ),
563
564            Intrinsic::ScopeId => output.push_str(
565                "
566                let scopeId = 0;
567            ",
568            ),
569
570            Intrinsic::SymbolResourceHandle => output.push_str(
571                "
572                const symbolRscHandle = Symbol('handle');
573            ",
574            ),
575
576            Intrinsic::SymbolResourceRep => output.push_str(
577                "
578                const symbolRscRep = Symbol.for('cabiRep');
579            ",
580            ),
581
582            Intrinsic::SymbolDispose => output.push_str(
583                "
584                const symbolDispose = Symbol.dispose || Symbol.for('dispose');
585            ",
586            ),
587
588            Intrinsic::ThrowInvalidBool => output.push_str(
589                "
590                function throwInvalidBool() {
591                    throw new TypeError('invalid variant discriminant for bool');
592                }
593            ",
594            ),
595
596            Intrinsic::ThrowUninitialized => output.push_str(
597                "
598                function throwUninitialized() {
599                    throw new TypeError('Wasm uninitialized use `await $init` first');
600                }
601            ",
602            ),
603
604            Intrinsic::DebugLog => {
605                let fn_name = Intrinsic::DebugLog.name();
606                output.push_str(&format!(
607                    "
608                    const {fn_name} = (...args) => {{
609                        if (!globalThis?.process?.env?.JCO_DEBUG) {{ return; }}
610                        console.debug(...args);
611                    }}
612                "
613                ));
614            }
615
616            Intrinsic::PromiseWithResolversPolyfill => {
617                output.push_str(
618                    r#"
619                    if (!Promise.withResolvers) {
620                        Promise.withResolvers = () => {
621                            let resolve;
622                            let reject;
623                            const promise = new Promise((res, rej) => {
624                                resolve = res;
625                                reject = rej;
626                            });
627                            return { promise, resolve, reject };
628                        };
629                    }
630                "#,
631                );
632            }
633
634            Intrinsic::AsyncEventCodeEnum => {
635                let name = Intrinsic::AsyncEventCodeEnum.name();
636                output.push_str(&format!(
637                    "
638                    const {name} = {{
639                        NONE: 'none',
640                        TASK_CANCELLED: 'task-cancelled',
641                        STREAM_READ: 'stream-read',
642                        STREAM_WRITE: 'stream-write',
643                        FUTURE_READ: 'future-read',
644                        FUTURE_WRITE: 'future-write',
645                    }};
646                "
647                ));
648            }
649
650            Intrinsic::IsBorrowedType => {
651                let debug_log_fn = Intrinsic::DebugLog.name();
652                let is_borrowed_type_fn = Intrinsic::IsBorrowedType.name();
653                let defined_resource_tables = Intrinsic::DefinedResourceTables.name();
654                output.push_str(&format!("
655                    function {is_borrowed_type_fn}(componentInstanceID, typeIdx) {{
656                        {debug_log_fn}('[{is_borrowed_type_fn}()] args', {{ componentInstanceID, typeIdx }});
657                        const table = {defined_resource_tables}[componentInstanceID];
658                        if (!table) {{ return false; }}
659                        const handle = table[(typeIdx << 1) + 1];
660                        if (!handle) {{ return false; }}
661                        const isOwned = (handle & T_FLAG) !== 0;
662                        return !isOwned;
663                    }}
664                "));
665            }
666
667            Intrinsic::ManagedBufferClass => {
668                let debug_log_fn = Intrinsic::DebugLog.name();
669                let managed_buffer_class = Intrinsic::ManagedBufferClass.name();
670                output.push_str(&format!(
671                    "
672                    class {managed_buffer_class} {{
673                        constructor(args) {{
674                            {debug_log_fn}('[{managed_buffer_class}#constructor()] args', args);
675                        }}
676                    }}
677                "
678                ));
679            }
680
681            Intrinsic::BufferManagerClass => {
682                let debug_log_fn = Intrinsic::DebugLog.name();
683                let buffer_manager_class = Intrinsic::BufferManagerClass.name();
684                let managed_buffer_class = Intrinsic::ManagedBufferClass.name();
685                output.push_str(&format!("
686                    class {buffer_manager_class} {{
687                        #buffers = new Map();
688                        #bufferIDs = new Map();
689
690                        private constructor() {{ }}
691
692                        getNextBufferID(componentInstanceID) {{
693                            const current = this.#bufferIDs.get(args.componentInstanceID, 0);
694                            if (typeof current === 'undefined') {{
695                                this.#bufferIDs.set(args.componentInstanceID, 1);
696                                return 0;
697                            }}
698                            this.#bufferIDs.set(args.componentInstanceID, current + 1);
699                            return current;
700                        }}
701
702                        createBuffer(args) {{
703                            {debug_log_fn}('[{buffer_manager_class}#create()] args', args);
704                            if (!args || typeof args !== 'object') {{ throw new TypeError('missing/invalid argument object'); }}
705                            if (!args.componentInstanceID) {{ throw new TypeError('missing/invalid component instance ID'); }}
706                            if (!args.start) {{ throw new TypeError('missing/invalid start pointer'); }}
707                            if (!args.len) {{ throw new TypeError('missing/invalid buffer length'); }}
708                            const {{ componentInstanceID, start, len, typeIdx }} = args;
709
710                            if (!this.#buffers.has(componentInstanceID)) {{
711                                this.#buffers.set(componentInstanceID, new Map());
712                            }}
713                            const instanceBuffers = this.#buffers.get(componentInstanceID);
714
715                            const nextBufID = this.getNextBufferID(args.componentInstanceID);
716
717                            // TODO: check alignment and bounds, if typeIdx is present
718                            instanceBuffers.set(nextBufID, new {managed_buffer_class}());
719
720                            return nextBufID;
721                        }}
722                    }}
723                "));
724            }
725
726            Intrinsic::GlobalBufferManager => {
727                let global_buffer_manager = Intrinsic::GlobalBufferManager.name();
728                let buffer_manager_class = Intrinsic::BufferManagerClass.name();
729                output.push_str(&format!(
730                    "
731                    const {global_buffer_manager} = new {buffer_manager_class}();
732                "
733                ));
734            }
735
736            Intrinsic::WriteAsyncEventToMemory => {
737                let debug_log_fn = Intrinsic::DebugLog.name();
738                let write_async_event_to_memory_fn = Intrinsic::WriteAsyncEventToMemory.name();
739                output.push_str(&format!("
740                    function {write_async_event_to_memory_fn}(memory, task, event, ptr) {{
741                        {debug_log_fn}('[{write_async_event_to_memory_fn}()] args', {{ memory, task, event, ptr }});
742                        throw new Error('{write_async_event_to_memory_fn}() not implemented');
743                    }}
744                "));
745            }
746
747            Intrinsic::RepTableClass => {
748                let debug_log_fn = Intrinsic::DebugLog.name();
749                let rep_table_class = Intrinsic::RepTableClass.name();
750                output.push_str(&format!("
751                    class {rep_table_class} {{
752                        #data = [0, null];
753
754                        insert(val) {{
755                            {debug_log_fn}('[{rep_table_class}#insert()] args', {{ val }});
756                            const freeIdx = this.#data[0];
757                            if (freeIdx === 0) {{
758                                this.#data.push(val);
759                                this.#data.push(null);
760                                return (this.#data.length >> 1) - 1;
761                            }}
762                            this.#data[0] = this.#data[freeIdx];
763                            const newFreeIdx = freeIdx << 1;
764                            this.#data[newFreeIdx] = val;
765                            this.#data[newFreeIdx + 1] = null;
766                            return free;
767                        }}
768
769                        get(rep) {{
770                            {debug_log_fn}('[{rep_table_class}#insert()] args', {{ rep }});
771                            const baseIdx = idx << 1;
772                            const val = this.#data[baseIdx];
773                            return val;
774                        }}
775
776                        contains(rep) {{
777                            {debug_log_fn}('[{rep_table_class}#insert()] args', {{ rep }});
778                            const baseIdx = idx << 1;
779                            return !!this.#data[baseIdx];
780                        }}
781
782                        remove(rep) {{
783                            {debug_log_fn}('[{rep_table_class}#insert()] args', {{ idx }});
784                            if (this.#data.length === 2) {{ throw new Error('invalid'); }}
785
786                            const baseIdx = idx << 1;
787                            const val = this.#data[baseIdx];
788                            if (val === 0) {{ throw new Error('invalid resource rep (cannot be 0)'); }}
789                            this.#data[baseIdx] = this.#data[0];
790                            this.#data[0] = idx;
791                            return val;
792                        }}
793
794                        clear() {{
795                            this.#data = [0, null];
796                        }}
797                    }}
798                "));
799            }
800        }
801    }
802
803    output
804}
805
806impl Intrinsic {
807    pub fn get_global_names() -> impl IntoIterator<Item = &'static str> {
808        JsHelperIntrinsic::get_global_names()
809            .into_iter()
810            .chain(vec![
811                // Intrinsic list exactly as below
812                "base64Compile",
813                "clampGuest",
814                "ComponentError",
815                "definedResourceTables",
816                "fetchCompile",
817                "finalizationRegistryCreate",
818                "getErrorPayload",
819                "handleTables",
820                "hasOwnProperty",
821                "imports",
822                "instantiateCore",
823                "isLE",
824                "scopeId",
825                "symbolCabiDispose",
826                "symbolCabiLower",
827                "symbolDispose",
828                "symbolRscHandle",
829                "symbolRscRep",
830                "T_FLAG",
831                "throwInvalidBool",
832                "throwUninitialized",
833                // JS Globals / non intrinsic names
834                "ArrayBuffer",
835                "BigInt",
836                "BigInt64Array",
837                "DataView",
838                "dv",
839                "emptyFunc",
840                "Error",
841                "fetch",
842                "Float32Array",
843                "Float64Array",
844                "Int32Array",
845                "Object",
846                "process",
847                "String",
848                "TextDecoder",
849                "TextEncoder",
850                "TypeError",
851                "Uint16Array",
852                "Uint8Array",
853                "URL",
854                "WebAssembly",
855            ])
856    }
857
858    pub fn name(&self) -> &'static str {
859        match self {
860            Intrinsic::JsHelper(i) => i.name(),
861            Intrinsic::Conversion(i) => i.name(),
862            Intrinsic::WebIdl(i) => i.name(),
863            Intrinsic::String(i) => i.name(),
864            Intrinsic::ErrCtx(i) => i.name(),
865            Intrinsic::AsyncTask(i) => i.name(),
866            Intrinsic::Waitable(i) => i.name(),
867            Intrinsic::Resource(i) => i.name(),
868            Intrinsic::Lift(i) => i.name(),
869            Intrinsic::AsyncStream(i) => i.name(),
870            Intrinsic::AsyncFuture(i) => i.name(),
871            Intrinsic::Component(i) => i.name(),
872
873            Intrinsic::Base64Compile => "base64Compile",
874            Intrinsic::ClampGuest => "clampGuest",
875            Intrinsic::ComponentError => "ComponentError",
876            Intrinsic::DefinedResourceTables => "definedResourceTables",
877            Intrinsic::FetchCompile => "fetchCompile",
878            Intrinsic::FinalizationRegistryCreate => "finalizationRegistryCreate",
879            Intrinsic::GetErrorPayload => "getErrorPayload",
880            Intrinsic::GetErrorPayloadString => "getErrorPayloadString",
881            Intrinsic::HandleTables => "handleTables",
882            Intrinsic::HasOwnProperty => "hasOwnProperty",
883            Intrinsic::InstantiateCore => "instantiateCore",
884            Intrinsic::IsLE => "isLE",
885            Intrinsic::ScopeId => "scopeId",
886            Intrinsic::SymbolCabiDispose => "symbolCabiDispose",
887            Intrinsic::SymbolCabiLower => "symbolCabiLower",
888            Intrinsic::SymbolDispose => "symbolDispose",
889            Intrinsic::SymbolResourceHandle => "symbolRscHandle",
890            Intrinsic::SymbolResourceRep => "symbolRscRep",
891            Intrinsic::ThrowInvalidBool => "throwInvalidBool",
892            Intrinsic::ThrowUninitialized => "throwUninitialized",
893
894            // Debugging
895            Intrinsic::DebugLog => "_debugLog",
896            Intrinsic::PromiseWithResolversPolyfill => unreachable!(),
897
898            // Types
899            Intrinsic::ConstantI32Min => "I32_MIN",
900            Intrinsic::ConstantI32Max => "I32_MAX",
901            Intrinsic::TypeCheckValidI32 => "_typeCheckValidI32",
902            Intrinsic::IsBorrowedType => "_isBorrowedType",
903
904            // Async
905            Intrinsic::GlobalAsyncDeterminism => "ASYNC_DETERMINISM",
906            Intrinsic::AwaitableClass => "Awaitable",
907            Intrinsic::CoinFlip => "_coinFlip",
908
909            // Data structures
910            Intrinsic::RepTableClass => "RepTable",
911
912            // Buffers for managed/synchronized writing to/from component memory
913            Intrinsic::ManagedBufferClass => "ManagedBuffer",
914            Intrinsic::BufferManagerClass => "BufferManager",
915            Intrinsic::GlobalBufferManager => "BUFFER_MGR",
916
917            // Helpers for working with async state
918            Intrinsic::AsyncEventCodeEnum => "ASYNC_EVENT_CODE",
919            Intrinsic::WriteAsyncEventToMemory => "writeAsyncEventToMemory",
920        }
921    }
922}