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 let check_may_leave_fn = args.require_intrinsic(ComponentIntrinsic::CheckMayLeave);
1131
1132 output.push_str(&format!(
1133 r#"
1134 function {suspending_import_wrapper_fn}(componentIdx, fn) {{
1135 return async function (...args) {{
1136 {check_may_leave_fn}(componentIdx);
1137 const saved = {global_current_task_meta_obj}[componentIdx] ?? null;
1138 try {{
1139 return await fn.apply(null, args);
1140 }} finally {{
1141 {global_current_task_meta_obj}[componentIdx] = saved;
1142 }}
1143 }};
1144 }}
1145 "#,
1146 ));
1147 }
1148
1149 Intrinsic::PlatformReadableStreamClass => {
1151 let name = self.name();
1152 uwriteln!(
1153 output,
1154 r#"
1155 if (!ReadableStream) {{
1156 throw new Error('builtin stream class [ReadableStream] is not available');
1157 }}
1158 const {name} = ReadableStream;
1159 "#
1160 );
1161 }
1162 }
1163 }
1164}
1165
1166#[cfg(test)]
1167mod tests {
1168 use super::*;
1169
1170 fn render(initial: impl IntoIterator<Item = Intrinsic>) -> (Source, BTreeSet<Intrinsic>) {
1171 let mut intrinsics = initial.into_iter().collect();
1172 let opts = TranspileOpts::default();
1173 let source = render_intrinsics(
1174 RenderIntrinsicsArgs::builder()
1175 .intrinsics(&mut intrinsics)
1176 .transpile_opts(&opts)
1177 .build(),
1178 );
1179 (source, intrinsics)
1180 }
1181
1182 #[test]
1183 fn renders_only_requested_and_discovered_intrinsics() {
1184 let (source, intrinsics) = render([Intrinsic::CoinFlip]);
1185
1186 assert_eq!(intrinsics, BTreeSet::from([Intrinsic::CoinFlip]));
1187 assert!(source.contains("Math.random()"));
1188 assert!(!source.contains("_debugLog"));
1189 assert!(!source.contains("class RepTable"));
1190 }
1191
1192 #[test]
1193 fn component_async_state_does_not_pull_in_create_stream_or_create_future() {
1194 let state = Intrinsic::Component(ComponentIntrinsic::ComponentAsyncStateClass);
1195 let create_stream = Intrinsic::AsyncStream(AsyncStreamIntrinsic::CreateStream);
1196 let get_stream_end = Intrinsic::AsyncStream(AsyncStreamIntrinsic::GetStreamEnd);
1197 let create_future = Intrinsic::AsyncFuture(AsyncFutureIntrinsic::CreateFuture);
1198 let get_future_end = Intrinsic::AsyncFuture(AsyncFutureIntrinsic::GetFutureEnd);
1199 let (source, intrinsics) = render([state]);
1200
1201 assert!(!intrinsics.contains(&create_stream));
1202 assert!(!intrinsics.contains(&get_stream_end));
1203 assert!(!intrinsics.contains(&create_future));
1204 assert!(!intrinsics.contains(&get_future_end));
1205 assert!(!source.contains("function createStream(cstate, args)"));
1206 assert!(!source.contains("function createFuture(cstate, args)"));
1207
1208 let (source, _) = render([create_stream]);
1209 assert!(source.contains("function createStream(cstate, args)"));
1210 assert!(!source.contains("function getStreamEnd(args)"));
1211 assert!(!source.contains("function createFuture(cstate, args)"));
1212
1213 let (source, _) = render([create_future]);
1214 assert!(source.contains("function createFuture(cstate, args)"));
1215 assert!(!source.contains("function getFutureEnd(args)"));
1216 assert!(!source.contains("function createStream(cstate, args)"));
1217
1218 let (_, intrinsics) = render([Intrinsic::Lift(LiftIntrinsic::LiftFlatStream)]);
1219 assert!(intrinsics.contains(&get_stream_end));
1220 assert!(!intrinsics.contains(&create_stream));
1221
1222 let (_, intrinsics) = render([Intrinsic::Lift(LiftIntrinsic::LiftFlatFuture)]);
1223 assert!(intrinsics.contains(&get_future_end));
1224 assert!(!intrinsics.contains(&create_future));
1225 }
1226
1227 #[test]
1228 fn stream_and_future_helpers_are_individual_intrinsics() {
1229 let helpers = [
1230 (
1231 Intrinsic::AsyncStream(AsyncStreamIntrinsic::CreateStream),
1232 "createStream",
1233 ),
1234 (
1235 Intrinsic::AsyncStream(AsyncStreamIntrinsic::GetStreamEnd),
1236 "getStreamEnd",
1237 ),
1238 (
1239 Intrinsic::AsyncStream(AsyncStreamIntrinsic::AddStreamEndToTable),
1240 "addStreamEndToTable",
1241 ),
1242 (
1243 Intrinsic::AsyncStream(AsyncStreamIntrinsic::DeleteStreamEnd),
1244 "deleteStreamEnd",
1245 ),
1246 (
1247 Intrinsic::AsyncStream(AsyncStreamIntrinsic::RemoveStreamEndFromTable),
1248 "removeStreamEndFromTable",
1249 ),
1250 (
1251 Intrinsic::AsyncFuture(AsyncFutureIntrinsic::CreateFuture),
1252 "createFuture",
1253 ),
1254 (
1255 Intrinsic::AsyncFuture(AsyncFutureIntrinsic::GetFutureEnd),
1256 "getFutureEnd",
1257 ),
1258 (
1259 Intrinsic::AsyncFuture(AsyncFutureIntrinsic::AddFutureEndToTable),
1260 "addFutureEndToTable",
1261 ),
1262 (
1263 Intrinsic::AsyncFuture(AsyncFutureIntrinsic::RemoveFutureEndFromTable),
1264 "removeFutureEndFromTable",
1265 ),
1266 ];
1267
1268 for &(intrinsic, name) in &helpers {
1269 let (source, intrinsics) = render([intrinsic]);
1270 assert!(intrinsics.contains(&intrinsic));
1271
1272 for &(_, other_name) in &helpers {
1273 assert_eq!(
1274 source.contains(&format!("function {other_name}(")),
1275 name == other_name,
1276 "rendering {name} unexpectedly changed whether {other_name} was emitted",
1277 );
1278 }
1279 }
1280 }
1281
1282 #[test]
1283 fn discovers_transitive_dependencies_in_dependency_order() {
1284 let transfer = Intrinsic::Resource(ResourceIntrinsic::ResourceTransferBorrow);
1285 let table_flag = Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag);
1286 let table_get = Intrinsic::Resource(ResourceIntrinsic::ResourceTableGet);
1287 let table_remove = Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove);
1288 let (source, intrinsics) = render([transfer]);
1289
1290 for dependency in [table_flag, table_get, table_remove] {
1291 assert!(intrinsics.contains(&dependency));
1292 }
1293
1294 let flag_position = source.find("const T_FLAG").unwrap();
1295 let get_position = source.find("function rscTableGet").unwrap();
1296 let remove_position = source.find("function rscTableRemove").unwrap();
1297 let transfer_position = source.find("function resourceTransferBorrow").unwrap();
1298 assert!(flag_position < get_position);
1299 assert!(flag_position < remove_position);
1300 assert!(get_position < transfer_position);
1301 assert!(remove_position < transfer_position);
1302 }
1303
1304 #[test]
1305 fn self_dependencies_are_cycle_safe() {
1306 let current_tasks = Intrinsic::AsyncTask(AsyncTaskIntrinsic::GlobalAsyncCurrentTaskMap);
1307 let (source, intrinsics) = render([current_tasks]);
1308
1309 assert_eq!(intrinsics, BTreeSet::from([current_tasks]));
1310 assert_eq!(
1311 source.matches("const ASYNC_TASKS_BY_COMPONENT_IDX").count(),
1312 1
1313 );
1314 }
1315
1316 #[test]
1317 fn resource_transfer_borrow_checks_source_handle() {
1318 let mut intrinsics = BTreeSet::from([Intrinsic::Resource(
1319 ResourceIntrinsic::ResourceTransferBorrow,
1320 )]);
1321 let opts = TranspileOpts::default();
1322 let source = render_intrinsics(
1323 RenderIntrinsicsArgs::builder()
1324 .intrinsics(&mut intrinsics)
1325 .transpile_opts(&opts)
1326 .build(),
1327 );
1328
1329 assert!(source.contains("function rscTableGet(table, handle)"));
1330 assert!(source.contains("function rscTableRemove(table, handle)"));
1331 assert!(source.contains("const { rep, own } = rscTableGet(fromTable, handle);"));
1332 assert!(source.contains("if (!own) rscTableRemove(fromTable, handle);"));
1333 }
1334
1335 #[test]
1339 fn future_read_write_emit_future_end_classes() {
1340 for (op, end_class) in [
1341 (AsyncFutureIntrinsic::FutureRead, "class FutureReadableEnd"),
1342 (AsyncFutureIntrinsic::FutureWrite, "class FutureWritableEnd"),
1343 ] {
1344 let mut intrinsics = BTreeSet::from([Intrinsic::AsyncFuture(op)]);
1345 let opts = TranspileOpts::default();
1346 let source = render_intrinsics(
1347 RenderIntrinsicsArgs::builder()
1348 .intrinsics(&mut intrinsics)
1349 .transpile_opts(&opts)
1350 .build(),
1351 );
1352
1353 assert!(source.contains(end_class), "missing {end_class}");
1354 assert!(source.contains("class FutureEnd"), "missing FutureEnd");
1355 }
1356 }
1357
1358 #[test]
1359 fn flat_flags_bigint_representation_is_opt_in() {
1360 fn render(flags_as_bigint: bool) -> Source {
1361 let mut intrinsics = BTreeSet::from([
1362 Intrinsic::Lift(LiftIntrinsic::LiftFlatFlags),
1363 Intrinsic::Lower(LowerIntrinsic::LowerFlatFlags),
1364 ]);
1365 let opts = TranspileOpts::builder()
1366 .name("test".into())
1367 .flags_as_bigint(flags_as_bigint)
1368 .build();
1369 render_intrinsics(
1370 RenderIntrinsicsArgs::builder()
1371 .intrinsics(&mut intrinsics)
1372 .transpile_opts(&opts)
1373 .build(),
1374 )
1375 }
1376
1377 let default_source = render(false);
1378 assert!(default_source.contains("val[name] = (bits & 1) === 1;"));
1379 assert!(default_source.contains("const flagObj = ctx.vals[0];"));
1380 assert!(!default_source.contains("val = BigInt(bits >>> 0);"));
1381
1382 let bigint_source = render(true);
1383 assert!(bigint_source.contains("val = BigInt(bits >>> 0);"));
1384 assert!(bigint_source.contains("typeof bigintFlags !== 'bigint'"));
1385 assert!(!bigint_source.contains("const flagObj = ctx.vals[0];"));
1386 }
1387}
1388
1389#[derive(Debug, Default, PartialEq, Eq)]
1391pub enum AsyncDeterminismProfile {
1392 #[default]
1394 Random,
1395
1396 #[allow(unused)]
1398 Deterministic,
1399}
1400
1401impl std::fmt::Display for AsyncDeterminismProfile {
1402 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1403 write!(
1404 f,
1405 "{}",
1406 match self {
1407 Self::Deterministic => "deterministic",
1408 Self::Random => "random",
1409 }
1410 )
1411 }
1412}
1413
1414#[derive(bon::Builder)]
1416#[non_exhaustive]
1417pub struct RenderIntrinsicsArgs<'a> {
1418 pub(crate) intrinsics: &'a mut BTreeSet<Intrinsic>,
1420 #[builder(default)]
1422 pub(crate) instantiation_occurred: bool,
1423 #[builder(default)]
1425 pub(crate) determinism_profile: AsyncDeterminismProfile,
1426 pub(crate) transpile_opts: &'a TranspileOpts,
1428 #[builder(default)]
1430 discovered_intrinsics: Mutex<BTreeSet<Intrinsic>>,
1431}
1432
1433impl RenderIntrinsicsArgs<'_> {
1434 pub fn require_intrinsic(&self, intrinsic: impl Into<Intrinsic>) -> &'static str {
1437 let intrinsic = intrinsic.into();
1438 self.discovered_intrinsics
1439 .lock()
1440 .expect("intrinsic dependency collector lock should not be poisoned")
1441 .insert(intrinsic);
1442 intrinsic.name()
1443 }
1444
1445 fn take_discovered_intrinsics(&self) -> BTreeSet<Intrinsic> {
1446 std::mem::take(
1447 &mut *self
1448 .discovered_intrinsics
1449 .lock()
1450 .expect("intrinsic dependency collector lock should not be poisoned"),
1451 )
1452 }
1453}
1454
1455pub fn render_intrinsics(mut args: RenderIntrinsicsArgs) -> Source {
1458 render_intrinsics_discovered(&mut args)
1459}
1460
1461fn render_intrinsics_discovered(args: &mut RenderIntrinsicsArgs<'_>) -> Source {
1462 let mut pending = args.intrinsics.clone();
1463 let mut rendered = BTreeMap::new();
1464 let mut dependencies = BTreeMap::new();
1465
1466 while let Some(intrinsic) = pending.pop_first() {
1467 if rendered.contains_key(&intrinsic) {
1468 continue;
1469 }
1470
1471 debug_assert!(args.take_discovered_intrinsics().is_empty());
1472 let mut source = Source::default();
1473 intrinsic.render(&mut source, args);
1474 let discovered = args.take_discovered_intrinsics();
1475 for dependency in &discovered {
1476 if !rendered.contains_key(dependency) {
1477 pending.insert(*dependency);
1478 }
1479 args.intrinsics.insert(*dependency);
1480 }
1481 dependencies.insert(intrinsic, discovered);
1482 rendered.insert(intrinsic, source);
1483 }
1484
1485 let mut output = Source::default();
1486 if args
1487 .intrinsics
1488 .contains(&Intrinsic::Conversion(ConversionIntrinsic::F32ToI32))
1489 || args
1490 .intrinsics
1491 .contains(&Intrinsic::Conversion(ConversionIntrinsic::I32ToF32))
1492 {
1493 output.push_str(
1494 "
1495 const i32ToF32I = new Int32Array(1);
1496 const i32ToF32F = new Float32Array(i32ToF32I.buffer);
1497 ",
1498 );
1499 }
1500
1501 if args
1502 .intrinsics
1503 .contains(&Intrinsic::Conversion(ConversionIntrinsic::F64ToI64))
1504 || args
1505 .intrinsics
1506 .contains(&Intrinsic::Conversion(ConversionIntrinsic::I64ToF64))
1507 {
1508 output.push_str(
1509 "
1510 const i64ToF64I = new BigInt64Array(1);
1511 const i64ToF64F = new Float64Array(i64ToF64I.buffer);
1512 ",
1513 );
1514 }
1515
1516 let mut visiting = BTreeSet::new();
1517 let mut emitted = BTreeSet::new();
1518 for intrinsic in args.intrinsics.iter().copied() {
1519 emit_intrinsic(
1520 intrinsic,
1521 &dependencies,
1522 &rendered,
1523 &mut visiting,
1524 &mut emitted,
1525 &mut output,
1526 );
1527 }
1528 output
1529}
1530
1531fn emit_intrinsic(
1532 intrinsic: Intrinsic,
1533 dependencies: &BTreeMap<Intrinsic, BTreeSet<Intrinsic>>,
1534 rendered: &BTreeMap<Intrinsic, Source>,
1535 visiting: &mut BTreeSet<Intrinsic>,
1536 emitted: &mut BTreeSet<Intrinsic>,
1537 output: &mut Source,
1538) {
1539 if emitted.contains(&intrinsic) || !visiting.insert(intrinsic) {
1540 return;
1541 }
1542
1543 if let Some(intrinsic_dependencies) = dependencies.get(&intrinsic) {
1544 for dependency in intrinsic_dependencies {
1545 emit_intrinsic(
1546 *dependency,
1547 dependencies,
1548 rendered,
1549 visiting,
1550 emitted,
1551 output,
1552 );
1553 }
1554 }
1555
1556 visiting.remove(&intrinsic);
1557 if emitted.insert(intrinsic) {
1558 output.push_str(
1559 rendered
1560 .get(&intrinsic)
1561 .expect("intrinsic should have been rendered"),
1562 );
1563 }
1564}
1565
1566impl Intrinsic {
1567 pub fn get_global_names() -> impl IntoIterator<Item = &'static str> {
1568 JsHelperIntrinsic::get_global_names()
1569 .into_iter()
1570 .chain(vec![
1571 "base64Compile",
1573 "clampGuest",
1574 "ComponentError",
1575 "WebAssemblyRuntimeError",
1576 "fetchCompile",
1577 "finalizationRegistryCreate",
1578 "getErrorPayload",
1579 "HANDLE_TABLES",
1580 "hasOwnProperty",
1581 "imports",
1582 "instantiateCore",
1583 "isLE",
1584 "scopeId",
1585 "symbolCabiDispose",
1586 "symbolCabiLower",
1587 "symbolDispose",
1588 "symbolAsyncIterator",
1589 "symbolIterator",
1590 "symbolRscHandle",
1591 "symbolRscRep",
1592 "T_FLAG",
1593 "throwInvalidBool",
1594 "throwUninitialized",
1595 "ArrayBuffer",
1597 "BigInt",
1598 "BigInt64Array",
1599 "DataView",
1600 "dv",
1601 "emptyFunc",
1602 "Error",
1603 "fetch",
1604 "Float32Array",
1605 "Float64Array",
1606 "Int32Array",
1607 "Object",
1608 "process",
1609 "String",
1610 "TextDecoder",
1611 "TextEncoder",
1612 "TypeError",
1613 "Uint16Array",
1614 "Uint8Array",
1615 "URL",
1616 "WebAssembly",
1617 "GlobalComponentMemories",
1618 ])
1619 }
1620
1621 pub fn name(&self) -> &'static str {
1622 match self {
1623 Intrinsic::JsHelper(i) => i.name(),
1624 Intrinsic::Conversion(i) => i.name(),
1625 Intrinsic::WebIdl(i) => i.name(),
1626 Intrinsic::String(i) => i.name(),
1627 Intrinsic::ErrCtx(i) => i.name(),
1628 Intrinsic::AsyncTask(i) => i.name(),
1629 Intrinsic::Waitable(i) => i.name(),
1630 Intrinsic::Resource(i) => i.name(),
1631 Intrinsic::Lift(i) => i.name(),
1632 Intrinsic::Lower(i) => i.name(),
1633 Intrinsic::AsyncStream(i) => i.name(),
1634 Intrinsic::AsyncFuture(i) => i.name(),
1635 Intrinsic::Component(i) => i.name(),
1636 Intrinsic::Host(i) => i.name(),
1637
1638 Intrinsic::Base64Compile => "base64Compile",
1639 Intrinsic::ClampGuest => "clampGuest",
1640 Intrinsic::ComponentError => "ComponentError",
1641 Intrinsic::WebAssemblyRuntimeError => "WebAssemblyRuntimeError",
1642 Intrinsic::FetchCompile => "fetchCompile",
1643 Intrinsic::FinalizationRegistryCreate => "finalizationRegistryCreate",
1644 Intrinsic::GetErrorPayload => "getErrorPayload",
1645 Intrinsic::GetErrorPayloadString => "getErrorPayloadString",
1646 Intrinsic::HandleTables => "HANDLE_TABLES",
1647 Intrinsic::HasOwnProperty => "hasOwnProperty",
1648 Intrinsic::InstantiateCore => "instantiateCore",
1649 Intrinsic::IsLE => "isLE",
1650 Intrinsic::ScopeId => "SCOPE_ID",
1651
1652 Intrinsic::SymbolCabiDispose => "symbolCabiDispose",
1653 Intrinsic::SymbolCabiLower => "symbolCabiLower",
1654 Intrinsic::SymbolDispose => "symbolDispose",
1655 Intrinsic::SymbolAsyncIterator => "symbolAsyncIterator",
1656 Intrinsic::SymbolIterator => "symbolIterator",
1657 Intrinsic::SymbolResourceHandle => "symbolRscHandle",
1658 Intrinsic::SymbolResourceRep => "symbolRscRep",
1659
1660 Intrinsic::ThrowInvalidBool => "throwInvalidBool",
1661 Intrinsic::ThrowUninitialized => "throwUninitialized",
1662
1663 Intrinsic::DebugLog => "_debugLog",
1665 Intrinsic::PromiseWithResolversPonyfill => "promiseWithResolvers",
1666
1667 Intrinsic::ConstantI32Min => "I32_MIN",
1669 Intrinsic::ConstantI32Max => "I32_MAX",
1670 Intrinsic::TypeCheckValidI32 => "_typeCheckValidI32",
1671 Intrinsic::TypeCheckAsyncFn => "_typeCheckAsyncFn",
1672 Intrinsic::AsyncFunctionCtor => "ASYNC_FN_CTOR",
1673
1674 Intrinsic::PlatformReadableStreamClass => "_PlatformReadableStream",
1676
1677 Intrinsic::GlobalAsyncDeterminism => "ASYNC_DETERMINISM",
1679 Intrinsic::CoinFlip => "_coinFlip",
1680
1681 Self::GlobalCurrentTaskMeta => "CURRENT_TASK_META",
1683 Self::GetGlobalCurrentTaskMetaFn => "_getGlobalCurrentTaskMeta",
1684 Self::SetGlobalCurrentTaskMetaFn => "_setGlobalCurrentTaskMeta",
1685 Self::WithGlobalCurrentTaskMetaFn => "_withGlobalCurrentTaskMeta",
1686 Self::WithGlobalCurrentTaskMetaFnAsync => "_withGlobalCurrentTaskMetaAsync",
1687 Self::ClearGlobalCurrentTaskMetaFn => "_clearCurrentTask",
1688 Self::SuspendingImportWrapperFn => "_suspendingImport",
1689
1690 Intrinsic::GlobalComponentMemoryMap => "GLOBAL_COMPONENT_MEMORY_MAP",
1692 Intrinsic::RegisterGlobalMemoryForComponent => "registerGlobalMemoryForComponent",
1693 Intrinsic::LookupMemoriesForComponent => "lookupMemoriesForComponent",
1694
1695 Intrinsic::RepTableClass => "RepTable",
1697
1698 Intrinsic::ManagedBufferClass => "ManagedBuffer",
1700 Intrinsic::BufferManagerClass => "BufferManager",
1701 Intrinsic::GlobalBufferManager => "BUFFER_MGR",
1702
1703 Intrinsic::AsyncEventCodeEnum => "ASYNC_EVENT_CODE",
1705 }
1706 }
1707}