1use 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#[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 PromiseWithResolversPonyfill,
66
67 DebugLog,
69
70 GlobalAsyncDeterminism,
72
73 CoinFlip,
75
76 ConstantI32Max,
78 ConstantI32Min,
79 TypeCheckValidI32,
80 TypeCheckAsyncFn,
81 AsyncFunctionCtor,
82
83 Base64Compile,
84 ClampGuest,
85 FetchCompile,
86
87 SymbolCabiDispose,
89 SymbolCabiLower,
90 SymbolResourceHandle,
91 SymbolResourceRep,
92 SymbolDispose,
93 SymbolAsyncIterator,
94 SymbolIterator,
95 ScopeId,
96 HandleTables,
97
98 PlatformReadableStreamClass,
102
103 FinalizationRegistryCreate,
105
106 ComponentError,
108 WebAssemblyRuntimeError,
109
110 GetErrorPayload,
112 GetErrorPayloadString,
113
114 ManagedBufferClass,
116
117 BufferManagerClass,
119
120 GlobalBufferManager,
122
123 RepTableClass,
127
128 AsyncEventCodeEnum,
130
131 IsLE,
133 ThrowInvalidBool,
134 ThrowUninitialized,
135 HasOwnProperty,
136 InstantiateCore,
137
138 GlobalComponentMemoryMap,
140
141 RegisterGlobalMemoryForComponent,
143
144 LookupMemoriesForComponent,
146
147 GlobalCurrentTaskMeta,
149
150 GetGlobalCurrentTaskMetaFn,
152
153 SetGlobalCurrentTaskMetaFn,
155
156 WithGlobalCurrentTaskMetaFn,
158
159 WithGlobalCurrentTaskMetaFnAsync,
161
162 ClearGlobalCurrentTaskMetaFn,
164
165 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 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 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 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 #[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#[derive(Debug, Default, PartialEq, Eq)]
1389pub enum AsyncDeterminismProfile {
1390 #[default]
1392 Random,
1393
1394 #[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#[derive(bon::Builder)]
1414#[non_exhaustive]
1415pub struct RenderIntrinsicsArgs<'a> {
1416 pub(crate) intrinsics: &'a mut BTreeSet<Intrinsic>,
1418 #[builder(default)]
1420 pub(crate) instantiation_occurred: bool,
1421 #[builder(default)]
1423 pub(crate) determinism_profile: AsyncDeterminismProfile,
1424 pub(crate) transpile_opts: &'a TranspileOpts,
1426 #[builder(default)]
1428 discovered_intrinsics: Mutex<BTreeSet<Intrinsic>>,
1429}
1430
1431impl RenderIntrinsicsArgs<'_> {
1432 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
1453pub 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 "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 "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 Intrinsic::DebugLog => "_debugLog",
1663 Intrinsic::PromiseWithResolversPonyfill => "promiseWithResolvers",
1664
1665 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 Intrinsic::PlatformReadableStreamClass => "_PlatformReadableStream",
1674
1675 Intrinsic::GlobalAsyncDeterminism => "ASYNC_DETERMINISM",
1677 Intrinsic::CoinFlip => "_coinFlip",
1678
1679 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 Intrinsic::GlobalComponentMemoryMap => "GLOBAL_COMPONENT_MEMORY_MAP",
1690 Intrinsic::RegisterGlobalMemoryForComponent => "registerGlobalMemoryForComponent",
1691 Intrinsic::LookupMemoriesForComponent => "lookupMemoriesForComponent",
1692
1693 Intrinsic::RepTableClass => "RepTable",
1695
1696 Intrinsic::ManagedBufferClass => "ManagedBuffer",
1698 Intrinsic::BufferManagerClass => "BufferManager",
1699 Intrinsic::GlobalBufferManager => "BUFFER_MGR",
1700
1701 Intrinsic::AsyncEventCodeEnum => "ASYNC_EVENT_CODE",
1703 }
1704 }
1705}