js_component_bindgen/
intrinsics.rs

1use crate::source::Source;
2use crate::uwrite;
3use std::collections::BTreeSet;
4use std::fmt::Write;
5
6#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
7pub enum Intrinsic {
8    Base64Compile,
9    ClampGuest,
10    ComponentError,
11    CurResourceBorrows,
12    DataView,
13    DefinedResourceTables,
14    EmptyFunc,
15    F32ToI32,
16    F64ToI64,
17    FetchCompile,
18    FinalizationRegistryCreate,
19    GetErrorPayload,
20    GetErrorPayloadString,
21    GlobalThisIdlProxy,
22    HandleTables,
23    HasOwnProperty,
24    I32ToF32,
25    I64ToF64,
26    InstantiateCore,
27    IsLE,
28    ResourceTableFlag,
29    ResourceTableCreateBorrow,
30    ResourceTableCreateOwn,
31    ResourceTableGet,
32    ResourceTableEnsureBorrowDrop,
33    ResourceTableRemove,
34    ResourceCallBorrows,
35    ResourceTransferBorrow,
36    ResourceTransferBorrowValidLifting,
37    ResourceTransferOwn,
38    ScopeId,
39    SymbolCabiDispose,
40    SymbolCabiLower,
41    SymbolResourceHandle,
42    SymbolResourceRep,
43    SymbolDispose,
44    ThrowInvalidBool,
45    ThrowUninitialized,
46    /// Implementation of <https://tc39.es/ecma262/#sec-tobigint64>
47    ToBigInt64,
48    /// Implementation of <https://tc39.es/ecma262/#sec-tobiguint64>
49    ToBigUint64,
50    /// Implementation of <https://tc39.es/ecma262/#sec-toint16>
51    ToInt16,
52    /// Implementation of <https://tc39.es/ecma262/#sec-toint32>
53    ToInt32,
54    /// Implementation of <https://tc39.es/ecma262/#sec-toint8>
55    ToInt8,
56    ToResultString,
57    /// Implementation of <https://tc39.es/ecma262/#sec-tostring>
58    ToString,
59    /// Implementation of <https://tc39.es/ecma262/#sec-touint16>
60    ToUint16,
61    /// Implementation of <https://tc39.es/ecma262/#sec-touint32>
62    ToUint32,
63    /// Implementation of <https://tc39.es/ecma262/#sec-touint8>
64    ToUint8,
65    Utf16Decoder,
66    Utf16Encode,
67    Utf8Decoder,
68    Utf8Encode,
69    Utf8EncodedLen,
70    ValidateGuestChar,
71    ValidateHostChar,
72}
73
74/// Emits the intrinsic `i` to this file and then returns the name of the
75/// intrinsic.
76pub fn render_intrinsics(
77    intrinsics: &mut BTreeSet<Intrinsic>,
78    no_nodejs_compat: bool,
79    instantiation: bool,
80) -> Source {
81    let mut output = Source::default();
82
83    // Handle intrinsic "dependence"
84    if intrinsics.contains(&Intrinsic::GetErrorPayload)
85        || intrinsics.contains(&Intrinsic::GetErrorPayloadString)
86    {
87        intrinsics.insert(Intrinsic::HasOwnProperty);
88    }
89    if intrinsics.contains(&Intrinsic::Utf16Encode) {
90        intrinsics.insert(Intrinsic::IsLE);
91    }
92
93    if intrinsics.contains(&Intrinsic::F32ToI32) || intrinsics.contains(&Intrinsic::I32ToF32) {
94        output.push_str(
95            "
96            const i32ToF32I = new Int32Array(1);
97            const i32ToF32F = new Float32Array(i32ToF32I.buffer);
98        ",
99        );
100    }
101    if intrinsics.contains(&Intrinsic::F64ToI64) || intrinsics.contains(&Intrinsic::I64ToF64) {
102        output.push_str(
103            "
104            const i64ToF64I = new BigInt64Array(1);
105            const i64ToF64F = new Float64Array(i64ToF64I.buffer);
106        ",
107        );
108    }
109    if intrinsics.contains(&Intrinsic::ResourceTransferBorrow)
110        || intrinsics.contains(&Intrinsic::ResourceTransferBorrowValidLifting)
111    {
112        intrinsics.insert(Intrinsic::ResourceTableCreateBorrow);
113    }
114
115    for i in intrinsics.iter() {
116        match i {
117            Intrinsic::Base64Compile => if !no_nodejs_compat {
118                output.push_str("
119                    const base64Compile = str => WebAssembly.compile(typeof Buffer !== 'undefined' ? Buffer.from(str, 'base64') : Uint8Array.from(atob(str), b => b.charCodeAt(0)));
120                ")
121            } else {
122                output.push_str("
123                    const base64Compile = str => WebAssembly.compile(Uint8Array.from(atob(str), b => b.charCodeAt(0)));
124                ")
125            },
126
127            Intrinsic::ClampGuest => output.push_str("
128                function clampGuest(i, min, max) {
129                    if (i < min || i > max) \
130                        throw new TypeError(`must be between ${min} and ${max}`);
131                    return i;
132                }
133            "),
134
135            Intrinsic::ComponentError => output.push_str("
136                class ComponentError extends Error {
137                    constructor (value) {
138                        const enumerable = typeof value !== 'string';
139                        super(enumerable ? `${String(value)} (see error.payload)` : value);
140                        Object.defineProperty(this, 'payload', { value, enumerable });
141                    }
142                }
143            "),
144
145            Intrinsic::CurResourceBorrows => output.push_str("
146                let curResourceBorrows = [];
147            "),
148
149            Intrinsic::DataView => output.push_str("
150                let dv = new DataView(new ArrayBuffer());
151                const dataView = mem => dv.buffer === mem.buffer ? dv : dv = new DataView(mem.buffer);
152            "),
153
154            Intrinsic::DefinedResourceTables => {},
155
156            Intrinsic::EmptyFunc => output.push_str("
157                const emptyFunc = () => {};
158            "),
159
160            Intrinsic::FinalizationRegistryCreate => output.push_str("
161                function finalizationRegistryCreate (unregister) {
162                    if (typeof FinalizationRegistry === 'undefined') {
163                        return { unregister () {} };
164                    }
165                    return new FinalizationRegistry(unregister);
166                }
167            "),
168
169            Intrinsic::F64ToI64 => output.push_str("
170                const f64ToI64 = f => (i64ToF64F[0] = f, i64ToF64I[0]);
171            "),
172
173            Intrinsic::FetchCompile => if !no_nodejs_compat {
174                output.push_str("
175                    const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
176                    let _fs;
177                    async function fetchCompile (url) {
178                        if (isNode) {
179                            _fs = _fs || await import('node:fs/promises');
180                            return WebAssembly.compile(await _fs.readFile(url));
181                        }
182                        return fetch(url).then(WebAssembly.compileStreaming);
183                    }
184                ")
185            } else {
186                output.push_str("
187                    const fetchCompile = url => fetch(url).then(WebAssembly.compileStreaming);
188                ")
189            },
190
191            Intrinsic::GetErrorPayload => {
192                let hop = Intrinsic::HasOwnProperty.name();
193                uwrite!(output, "
194                    function getErrorPayload(e) {{
195                        if (e && {hop}.call(e, 'payload')) return e.payload;
196                        if (e instanceof Error) throw e;
197                        return e;
198                    }}
199                ")
200            },
201
202            Intrinsic::GetErrorPayloadString => {
203                let hop = Intrinsic::HasOwnProperty.name();
204                uwrite!(output, "
205                    function getErrorPayloadString(e) {{
206                        if (e && {hop}.call(e, 'payload')) return e.payload;
207                        if (e instanceof Error) return e.message;
208                        return e;
209                    }}
210                ")
211            },
212
213            Intrinsic::GlobalThisIdlProxy => output.push_str(r#"
214                var idlProxy;
215                function globalThisIdlProxy () {
216                    if (idlProxy) return idlProxy;
217                    const innerSymbol = Symbol('inner');
218                    const isProxySymbol = Symbol('isProxy');
219                    const uppercaseRegex = /html|Html|dom|Dom/g;
220                    const globalNames = ['Window', 'WorkerGlobalScope'];
221                    function proxy(target, fake = {}) {
222                        const origTarget = target;
223                        return new Proxy(fake, {
224                            get: (_, prop, receiver) => {
225                                if (prop === innerSymbol) return origTarget;
226                                if (prop === isProxySymbol) return true;
227                                if (typeof prop !== 'string') return maybeProxy(Reflect.get(origTarget, prop));
228                                if (origTarget === globalThis && prop.startsWith('get') && globalNames.includes(prop.slice(3))) {
229                                    return () => receiver;
230                                }
231                                prop = prop.replaceAll(uppercaseRegex, x => x.toUpperCase());
232                                if (prop.startsWith('set')) return val => Reflect.set(origTarget, `${prop[3].toLowerCase()}${prop.slice(4)}`, val);
233                                if (prop.startsWith('as')) return () => receiver;
234                                const res = Reflect.get(origTarget, prop);
235                                if (res === undefined && prop[0].toUpperCase() === prop[0]) {
236                                    return Object.getPrototypeOf(globalThis[`${prop[0].toLowerCase()}${prop.slice(1)}`]).constructor;
237                                }
238                                return maybeProxy(res, prop);
239                            },
240                            apply: (_, thisArg, args) => {
241                                if (args.length === 1 && Array.isArray(args[0]) && origTarget.length === 0) args = args[0];
242                                const res = Reflect.apply(origTarget, proxyInner(thisArg), args.map(a =>  a[isProxySymbol] ? proxyInner(a) : a));
243                                return typeof res === 'object' ? proxy(res) : res;
244                            },
245                            getPrototypeOf: _ => Reflect.getPrototypeOf(origTarget),
246                            construct: (_, argArray, newTarget) => maybeProxy(Reflect.construct(origTarget, argArray, newTarget)),
247                            defineProperty: (_, property, attributes) => maybeProxy(Reflect.defineProperty(origTarget, property, attributes)),
248                            deleteProperty: (_, p) => maybeProxy(Reflect.deleteProperty(origTarget, p)),
249                            getOwnPropertyDescriptor: (_, p) => Reflect.getOwnPropertyDescriptor(origTarget, p),
250                            has: (_, p) => maybeProxy(Reflect.has(origTarget, p)),
251                            isExtensible: (_) => maybeProxy(Reflect.isExtensible(origTarget)),
252                            ownKeys: _ => maybeProxy(Reflect.ownKeys(origTarget)),
253                            preventExtensions: _ => maybeProxy(Reflect.preventExtensions(origTarget)),
254                            set: (_, p, newValue, receiver) => maybeProxy(Reflect.set(origTarget, p, newValue, receiver)),
255                            setPrototypeOf: (_, v) => maybeProxy(Reflect.setPrototypeOf(origTarget, v)),
256                        });
257                    }
258                    function maybeProxy(res, prop) {
259                        // Catch Class lookups
260                        if (typeof res === "function" && res.prototype?.constructor === res) return res;
261                        // Catch "regular" function calls
262                        if (typeof res === 'function') return proxy(res, () => {});
263                        // Catch all other objects
264                        if (typeof res === 'object' && res !== null) return () => proxy(res);
265                        return res;
266                    }
267                    const proxyInner = proxy => proxy ? proxy[innerSymbol] : proxy;
268                    return (idlProxy = proxy(globalThis));
269                };
270            "#),
271
272            Intrinsic::HandleTables => output.push_str("
273                const handleTables = [];
274            "),
275
276            Intrinsic::HasOwnProperty => output.push_str("
277                const hasOwnProperty = Object.prototype.hasOwnProperty;
278            "),
279
280            Intrinsic::I32ToF32 => output.push_str("
281                const i32ToF32 = i => (i32ToF32I[0] = i, i32ToF32F[0]);
282            "),
283            Intrinsic::F32ToI32 => output.push_str("
284                const f32ToI32 = f => (i32ToF32F[0] = f, i32ToF32I[0]);
285            "),
286            Intrinsic::I64ToF64 => output.push_str("
287                const i64ToF64 = i => (i64ToF64I[0] = i, i64ToF64F[0]);
288            "),
289
290            Intrinsic::InstantiateCore => if !instantiation {
291                output.push_str("
292                    const instantiateCore = WebAssembly.instantiate;
293                ")
294            },
295
296            Intrinsic::IsLE => output.push_str("
297                const isLE = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
298            "),
299
300            Intrinsic::ResourceCallBorrows => output.push_str("let resourceCallBorrows = [];"),
301
302            //
303            // # Resource table slab implementation
304            //
305            // Resource table slab implementation on top of a fixed "SMI" array in JS engines,
306            // a fixed contiguous array of u32s, for performance. We don't use a typed array because
307            // we need resizability without reserving a large buffer.
308            //
309            // The flag bit for all data values is 1 << 30. We avoid the use of the highest bit
310            // entirely to not trigger SMI deoptimization.
311            //
312            // Each entry consists of a pair of u32s, either a free list entry, or a data entry.
313            //
314            // ## Free List Entries:
315            //
316            //  |    index (x, u30)   |       ~unused~      |
317            //  |------ 32 bits ------|------ 32 bits ------|
318            //  | 01xxxxxxxxxxxxxxxxx | ################### |
319            //
320            // Free list entries use only the first value in the pair, with the high bit always set
321            // to indicate that the pair is part of the free list. The first pair of entries at
322            // indices 0 and 1 is the free list head, with the initial values of 1 << 30 and 0
323            // respectively. Removing the 1 << 30 flag gives 0, which indicates the end of the free
324            // list.
325            //
326            // ## Data Entries:
327            //
328            //  |    scope (x, u30)   | own(o), rep(x, u30) |
329            //  |------ 32 bits ------|------ 32 bits ------|
330            //  | 00xxxxxxxxxxxxxxxxx | 0oxxxxxxxxxxxxxxxxx |
331            //
332            // Data entry pairs consist of a first u30 scope entry and a second rep entry. The field
333            // is only called the scope for interface shape consistency, but is actually used for the
334            // ref count for own handles and the scope id for borrow handles. The high bit is never
335            // set for this first entry to distinguish the pair from the free list. The second item
336            // in the pair is the rep for  the resource, with the high bit in this entry indicating
337            // if it is an own handle.
338            //
339            // The free list numbering and the handle numbering are the same, indexing by pair, so to
340            // get from a handle or free list numbering to an index, we multiply by two.
341            //
342            // For example, to access a handle n, we read the pair of values n * 2 and n * 2 + 1 in
343            // the array to get the context and rep respectively. If the high bit is set on the
344            // context, we throw for an invalid handle. The rep value is masked out from the
345            // ownership high bit, also throwing for an invalid zero rep.
346            //
347            Intrinsic::ResourceTableFlag => output.push_str("
348                const T_FLAG = 1 << 30;
349            "),
350
351            Intrinsic::ResourceTableCreateBorrow => output.push_str("
352                function rscTableCreateBorrow (table, rep) {
353                    const free = table[0] & ~T_FLAG;
354                    if (free === 0) {
355                        table.push(scopeId);
356                        table.push(rep);
357                        return (table.length >> 1) - 1;
358                    }
359                    table[0] = table[free];
360                    table[free << 1] = scopeId;
361                    table[(free << 1) + 1] = rep;
362                    return free;
363                }
364            "),
365
366            Intrinsic::ResourceTableCreateOwn => output.push_str("
367                function rscTableCreateOwn (table, rep) {
368                    const free = table[0] & ~T_FLAG;
369                    if (free === 0) {
370                        table.push(0);
371                        table.push(rep | T_FLAG);
372                        return (table.length >> 1) - 1;
373                    }
374                    table[0] = table[free << 1];
375                    table[free << 1] = 0;
376                    table[(free << 1) + 1] = rep | T_FLAG;
377                    return free;
378                }
379            "),
380
381            Intrinsic::ResourceTableGet => output.push_str("
382                function rscTableGet (table, handle) {
383                    const scope = table[handle << 1];
384                    const val = table[(handle << 1) + 1];
385                    const own = (val & T_FLAG) !== 0;
386                    const rep = val & ~T_FLAG;
387                    if (rep === 0 || (scope & T_FLAG) !== 0) throw new TypeError('Invalid handle');
388                    return { rep, scope, own };
389                }
390            "),
391
392            Intrinsic::ResourceTableEnsureBorrowDrop => output.push_str("
393                function rscTableEnsureBorrowDrop (table, handle, scope) {
394                    if (table[handle << 1] === scope)
395                        throw new TypeError('Resource borrow was not dropped at end of call');
396                }
397            "),
398
399            Intrinsic::ResourceTableRemove => output.push_str("
400                function rscTableRemove (table, handle) {
401                    const scope = table[handle << 1];
402                    const val = table[(handle << 1) + 1];
403                    const own = (val & T_FLAG) !== 0;
404                    const rep = val & ~T_FLAG;
405                    if (val === 0 || (scope & T_FLAG) !== 0) throw new TypeError('Invalid handle');
406                    table[handle << 1] = table[0] | T_FLAG;
407                    table[0] = handle | T_FLAG;
408                    return { rep, scope, own };
409                }
410            "),
411
412            // For own transfer, in the case of a resource transfer where that resource is never dropped,
413            // it is possible to transfer in to a table that is otherwise fully uninitialized by the
414            // bindgen.
415            Intrinsic::ResourceTransferBorrow => {
416                let handle_tables = Intrinsic::HandleTables.name();
417                let resource_borrows = Intrinsic::ResourceCallBorrows.name();
418                let rsc_table_remove = Intrinsic::ResourceTableRemove.name();
419                let rsc_table_create_borrow = Intrinsic::ResourceTableCreateBorrow.name();
420                let defined_resource_tables = Intrinsic::DefinedResourceTables.name();
421                output.push_str(&format!("
422                    function resourceTransferBorrow(handle, fromTid, toTid) {{
423                        const fromTable = {handle_tables}[fromTid];
424                        const isOwn = (fromTable[(handle << 1) + 1] & T_FLAG) !== 0;
425                        const rep = isOwn ? fromTable[(handle << 1) + 1] & ~T_FLAG : {rsc_table_remove}(fromTable, handle).rep;
426                        if ({defined_resource_tables}[toTid]) return rep;
427                        const toTable = {handle_tables}[toTid] || ({handle_tables}[toTid] = [T_FLAG, 0]);
428                        const newHandle = {rsc_table_create_borrow}(toTable, rep);
429                        {resource_borrows}.push({{ rid: toTid, handle: newHandle }});
430                        return newHandle;
431                    }}
432                "));
433            },
434
435            Intrinsic::ResourceTransferBorrowValidLifting => {
436                let handle_tables = Intrinsic::HandleTables.name();
437                let rsc_table_remove = Intrinsic::ResourceTableRemove.name();
438                let rsc_table_create_borrow = Intrinsic::ResourceTableCreateBorrow.name();
439                let defined_resource_tables = Intrinsic::DefinedResourceTables.name();
440                output.push_str(&format!("
441                    function resourceTransferBorrowValidLifting(handle, fromTid, toTid) {{
442                        const fromTable = {handle_tables}[fromTid];
443                        const isOwn = (fromTable[(handle << 1) + 1] & T_FLAG) !== 0;
444                        const rep = isOwn ? fromTable[(handle << 1) + 1] & ~T_FLAG : {rsc_table_remove}(fromTable, handle).rep;
445                        if ({defined_resource_tables}[toTid]) return rep;
446                        const toTable = {handle_tables}[toTid] || ({handle_tables}[toTid] = [T_FLAG, 0]);
447                        return {rsc_table_create_borrow}(toTable, rep);
448                    }}
449                "));
450            },
451
452            Intrinsic::ResourceTransferOwn => {
453                let handle_tables = Intrinsic::HandleTables.name();
454                let rsc_table_remove = Intrinsic::ResourceTableRemove.name();
455                let rsc_table_create_own = Intrinsic::ResourceTableCreateOwn.name();
456                output.push_str(&format!("
457                    function resourceTransferOwn(handle, fromTid, toTid) {{
458                        const {{ rep }} = {rsc_table_remove}({handle_tables}[fromTid], handle);
459                        const toTable = {handle_tables}[toTid] || ({handle_tables}[toTid] = [T_FLAG, 0]);
460                        return {rsc_table_create_own}(toTable, rep);
461                    }}
462                "));
463            },
464
465            Intrinsic::SymbolCabiDispose => output.push_str("
466                const symbolCabiDispose = Symbol.for('cabiDispose');
467            "),
468
469            Intrinsic::SymbolCabiLower => output.push_str("
470                const symbolCabiLower = Symbol.for('cabiLower');
471            "),
472
473            Intrinsic::ScopeId => output.push_str("
474                let scopeId = 0;
475            "),
476
477            Intrinsic::SymbolResourceHandle => output.push_str("
478                const symbolRscHandle = Symbol('handle');
479            "),
480
481            Intrinsic::SymbolResourceRep => output.push_str("
482                const symbolRscRep = Symbol.for('cabiRep');
483            "),
484
485            Intrinsic::SymbolDispose => output.push_str("
486                const symbolDispose = Symbol.dispose || Symbol.for('dispose');
487            "),
488
489            Intrinsic::ThrowInvalidBool => output.push_str("
490                function throwInvalidBool() {
491                    throw new TypeError('invalid variant discriminant for bool');
492                }
493            "),
494
495            Intrinsic::ThrowUninitialized => output.push_str("
496                function throwUninitialized() {
497                    throw new TypeError('Wasm uninitialized use `await $init` first');
498                }
499            "),
500
501            Intrinsic::ToBigInt64 => output.push_str("
502                const toInt64 = val => BigInt.asIntN(64, BigInt(val));
503            "),
504
505            Intrinsic::ToBigUint64 => output.push_str("
506                const toUint64 = val => BigInt.asUintN(64, BigInt(val));
507            "),
508
509            Intrinsic::ToInt16 => output.push_str("
510                function toInt16(val) {
511                    val >>>= 0;
512                    val %= 2 ** 16;
513                    if (val >= 2 ** 15) {
514                        val -= 2 ** 16;
515                    }
516                    return val;
517                }
518            "),
519
520            Intrinsic::ToInt32 => output.push_str("
521                function toInt32(val) {
522                    return val >> 0;
523                }
524            "),
525
526            Intrinsic::ToInt8 => output.push_str("
527                function toInt8(val) {
528                    val >>>= 0;
529                    val %= 2 ** 8;
530                    if (val >= 2 ** 7) {
531                        val -= 2 ** 8;
532                    }
533                    return val;
534                }
535            "),
536
537            Intrinsic::ToResultString => output.push_str("
538                function toResultString(obj) {
539                    return JSON.stringify(obj, (_, v) => {
540                        if (v && Object.getPrototypeOf(v) === Uint8Array.prototype) {
541                            return `[${v[Symbol.toStringTag]} (${v.byteLength})]`;
542                        } else if (typeof v === 'bigint') {
543                            return v.toString();
544                        }
545                        return v;
546                    });
547                }
548            "),
549
550            // Calling `String` almost directly calls `ToString`, except that it also allows symbols,
551            // which is why we have the symbol-rejecting branch above.
552            //
553            // Definition of `String`: https://tc39.es/ecma262/#sec-string-constructor-string-value
554            Intrinsic::ToString => output.push_str("
555                function toString(val) {
556                    if (typeof val === 'symbol') throw new TypeError('symbols cannot be converted to strings');
557                    return String(val);
558                }
559            "),
560
561            Intrinsic::ToUint16 => output.push_str("
562                function toUint16(val) {
563                    val >>>= 0;
564                    val %= 2 ** 16;
565                    return val;
566                }
567            "),
568
569            Intrinsic::ToUint32 => output.push_str("
570                function toUint32(val) {
571                    return val >>> 0;
572                }
573            "),
574
575            Intrinsic::ToUint8 => output.push_str("
576                function toUint8(val) {
577                    val >>>= 0;
578                    val %= 2 ** 8;
579                    return val;
580                }
581            "),
582
583            Intrinsic::Utf16Decoder => output.push_str("
584                const utf16Decoder = new TextDecoder('utf-16');
585            "),
586
587            Intrinsic::Utf16Encode => {
588                let is_le = Intrinsic::IsLE.name();
589                uwrite!(output, "
590                    function utf16Encode (str, realloc, memory) {{
591                        const len = str.length, ptr = realloc(0, 0, 2, len * 2), out = new Uint16Array(memory.buffer, ptr, len);
592                        let i = 0;
593                        if ({is_le}) {{
594                            while (i < len) out[i] = str.charCodeAt(i++);
595                        }} else {{
596                            while (i < len) {{
597                                const ch = str.charCodeAt(i);
598                                out[i++] = (ch & 0xff) << 8 | ch >>> 8;
599                            }}
600                        }}
601                        return ptr;
602                    }}
603                ");
604            },
605
606            Intrinsic::Utf8Decoder => output.push_str("
607                const utf8Decoder = new TextDecoder();
608            "),
609
610            Intrinsic::Utf8EncodedLen => {},
611
612            Intrinsic::Utf8Encode => output.push_str("
613                const utf8Encoder = new TextEncoder();
614
615                let utf8EncodedLen = 0;
616                function utf8Encode(s, realloc, memory) {
617                    if (typeof s !== 'string') \
618                        throw new TypeError('expected a string');
619                    if (s.length === 0) {
620                        utf8EncodedLen = 0;
621                        return 1;
622                    }
623                    let buf = utf8Encoder.encode(s);
624                    let ptr = realloc(0, 0, 1, buf.length);
625                    new Uint8Array(memory.buffer).set(buf, ptr);
626                    utf8EncodedLen = buf.length;
627                    return ptr;
628                }
629            "),
630
631            Intrinsic::ValidateGuestChar => output.push_str("
632                function validateGuestChar(i) {
633                    if ((i > 0x10ffff) || (i >= 0xd800 && i <= 0xdfff)) \
634                        throw new TypeError(`not a valid char`);
635                    return String.fromCodePoint(i);
636                }
637            "),
638
639            // TODO: this is incorrect. It at least allows strings of length > 0
640            // but it probably doesn't do the right thing for unicode or invalid
641            // utf16 strings either.
642            Intrinsic::ValidateHostChar => output.push_str("
643                function validateHostChar(s) {
644                    if (typeof s !== 'string') \
645                        throw new TypeError(`must be a string`);
646                    return s.codePointAt(0);
647                }
648            "),
649      }
650    }
651
652    output
653}
654
655impl Intrinsic {
656    pub fn get_global_names() -> &'static [&'static str] {
657        &[
658            // Intrinsic list exactly as below
659            "base64Compile",
660            "clampGuest",
661            "ComponentError",
662            "curResourceBorrows",
663            "dataView",
664            "definedResourceTables",
665            "emptyFunc",
666            "f32ToI32",
667            "f64ToI64",
668            "fetchCompile",
669            "finalizationRegistryCreate",
670            "getErrorPayload",
671            "globalThisIdlProxy",
672            "handleTables",
673            "hasOwnProperty",
674            "i32ToF32",
675            "i64ToF64",
676            "imports",
677            "instantiateCore",
678            "isLE",
679            "resourceCallBorrows",
680            "resourceTransferBorrow",
681            "resourceTransferBorrowValidLifting",
682            "resourceTransferOwn",
683            "rscTableCreateBorrow",
684            "rscTableCreateOwn",
685            "rscTableGet",
686            "rscTableRemove",
687            "rscTableTryGet",
688            "scopeId",
689            "symbolCabiDispose",
690            "symbolCabiLower",
691            "symbolDispose",
692            "symbolRscHandle",
693            "symbolRscRep",
694            "T_FLAG",
695            "throwInvalidBool",
696            "throwUninitialized",
697            "toInt16",
698            "toInt32",
699            "toInt64",
700            "toInt8",
701            "toResultString",
702            "toString",
703            "toUint16",
704            "toUint32",
705            "toUint64",
706            "toUint8",
707            "utf16Decoder",
708            "utf16Encode",
709            "utf8Decoder",
710            "utf8Encode",
711            "utf8EncodedLen",
712            "validateGuestChar",
713            "validateHostChar",
714            // JS Globals / non intrinsic names
715            "ArrayBuffer",
716            "BigInt",
717            "BigInt64Array",
718            "DataView",
719            "dv",
720            "emptyFunc",
721            "Error",
722            "fetch",
723            "Float32Array",
724            "Float64Array",
725            "Int32Array",
726            "Object",
727            "process",
728            "String",
729            "TextDecoder",
730            "TextEncoder",
731            "toUint64",
732            "TypeError",
733            "Uint16Array",
734            "Uint8Array",
735            "URL",
736            "WebAssembly",
737        ]
738    }
739
740    pub fn name(&self) -> &'static str {
741        match self {
742            Intrinsic::Base64Compile => "base64Compile",
743            Intrinsic::ClampGuest => "clampGuest",
744            Intrinsic::ComponentError => "ComponentError",
745            Intrinsic::CurResourceBorrows => "curResourceBorrows",
746            Intrinsic::DataView => "dataView",
747            Intrinsic::DefinedResourceTables => "definedResourceTables",
748            Intrinsic::EmptyFunc => "emptyFunc",
749            Intrinsic::F32ToI32 => "f32ToI32",
750            Intrinsic::F64ToI64 => "f64ToI64",
751            Intrinsic::FetchCompile => "fetchCompile",
752            Intrinsic::FinalizationRegistryCreate => "finalizationRegistryCreate",
753            Intrinsic::GetErrorPayload => "getErrorPayload",
754            Intrinsic::GetErrorPayloadString => "getErrorPayloadString",
755            Intrinsic::GlobalThisIdlProxy => "globalThisIdlProxy",
756            Intrinsic::HandleTables => "handleTables",
757            Intrinsic::HasOwnProperty => "hasOwnProperty",
758            Intrinsic::I32ToF32 => "i32ToF32",
759            Intrinsic::I64ToF64 => "i64ToF64",
760            Intrinsic::InstantiateCore => "instantiateCore",
761            Intrinsic::IsLE => "isLE",
762            Intrinsic::ResourceCallBorrows => "resourceCallBorrows",
763            Intrinsic::ResourceTableFlag => "T_FLAG",
764            Intrinsic::ResourceTableCreateBorrow => "rscTableCreateBorrow",
765            Intrinsic::ResourceTableCreateOwn => "rscTableCreateOwn",
766            Intrinsic::ResourceTableGet => "rscTableGet",
767            Intrinsic::ResourceTableEnsureBorrowDrop => "rscTableTryGet",
768            Intrinsic::ResourceTableRemove => "rscTableRemove",
769            Intrinsic::ResourceTransferBorrow => "resourceTransferBorrow",
770            Intrinsic::ResourceTransferBorrowValidLifting => "resourceTransferBorrowValidLifting",
771            Intrinsic::ResourceTransferOwn => "resourceTransferOwn",
772            Intrinsic::ScopeId => "scopeId",
773            Intrinsic::SymbolCabiDispose => "symbolCabiDispose",
774            Intrinsic::SymbolCabiLower => "symbolCabiLower",
775            Intrinsic::SymbolDispose => "symbolDispose",
776            Intrinsic::SymbolResourceHandle => "symbolRscHandle",
777            Intrinsic::SymbolResourceRep => "symbolRscRep",
778            Intrinsic::ThrowInvalidBool => "throwInvalidBool",
779            Intrinsic::ThrowUninitialized => "throwUninitialized",
780            Intrinsic::ToBigInt64 => "toInt64",
781            Intrinsic::ToBigUint64 => "toUint64",
782            Intrinsic::ToInt16 => "toInt16",
783            Intrinsic::ToInt32 => "toInt32",
784            Intrinsic::ToInt8 => "toInt8",
785            Intrinsic::ToResultString => "toResultString",
786            Intrinsic::ToString => "toString",
787            Intrinsic::ToUint16 => "toUint16",
788            Intrinsic::ToUint32 => "toUint32",
789            Intrinsic::ToUint8 => "toUint8",
790            Intrinsic::Utf16Decoder => "utf16Decoder",
791            Intrinsic::Utf16Encode => "utf16Encode",
792            Intrinsic::Utf8Decoder => "utf8Decoder",
793            Intrinsic::Utf8Encode => "utf8Encode",
794            Intrinsic::Utf8EncodedLen => "utf8EncodedLen",
795            Intrinsic::ValidateGuestChar => "validateGuestChar",
796            Intrinsic::ValidateHostChar => "validateHostChar",
797        }
798    }
799}