1use crate::component::dfg::{
29 AdapterId, CanonicalOptionsDataModel, ComponentDfg, CoreDef, Export, Instance, SideEffect,
30 Trampoline,
31};
32use crate::component::{
33 ComponentTypesBuilder, DataModel, RuntimeComponentInstanceIndex, UnsafeIntrinsic,
34};
35use cranelift_entity::EntitySet;
36
37#[derive(Clone, Copy, PartialEq, Eq, Debug)]
39enum CoreCallable {
40 HostImport,
45
46 Adapter {
48 async_lift: bool,
50 },
51
52 ContextAccess,
54
55 ThreadStateBuiltin,
59
60 Inert,
63}
64
65impl CoreCallable {
66 fn may_touch_thread_state(self) -> bool {
69 match self {
70 CoreCallable::HostImport => true,
72
73 CoreCallable::Adapter { async_lift } => async_lift,
82
83 CoreCallable::ContextAccess | CoreCallable::ThreadStateBuiltin => true,
84
85 CoreCallable::Inert => false,
86 }
87 }
88}
89
90#[derive(Clone, Copy, PartialEq, Eq, Debug)]
93struct AdapterFacts {
94 callee: RuntimeComponentInstanceIndex,
97
98 any_async: bool,
101
102 any_handle: bool,
104}
105
106#[derive(Default)]
111struct ThreadTransparencyBuilder {
112 opaque: EntitySet<RuntimeComponentInstanceIndex>,
114}
115
116impl ThreadTransparencyBuilder {
117 fn observe(&mut self, instance: RuntimeComponentInstanceIndex, callable: CoreCallable) {
119 if callable.may_touch_thread_state() {
120 self.opaque.insert(instance);
121 }
122 }
123
124 fn finish(self) -> ThreadTransparency {
126 ThreadTransparency {
127 opaque: self.opaque,
128 }
129 }
130}
131
132struct ThreadTransparency {
134 opaque: EntitySet<RuntimeComponentInstanceIndex>,
136}
137
138impl ThreadTransparency {
139 fn adapter_is_transparent(&self, facts: AdapterFacts) -> bool {
142 if facts.any_async {
144 return false;
145 }
146
147 if facts.any_handle {
150 return false;
151 }
152
153 !self.opaque.contains(facts.callee)
154 }
155}
156
157pub fn transparent_adapters(
160 dfg: &ComponentDfg,
161 types: &ComponentTypesBuilder,
162) -> EntitySet<AdapterId> {
163 let mut builder = ThreadTransparencyBuilder::default();
164
165 for (_, (_, trampoline)) in dfg.trampolines.iter() {
167 let (instance, callable) = trampoline_callable(dfg, trampoline);
168 builder.observe(instance, callable);
169 }
170
171 for (_, adapter) in dfg.adapters.iter() {
173 let async_lift = types[adapter.lift_ty].async_;
174 builder.observe(
175 adapter.lower_options.instance,
176 CoreCallable::Adapter { async_lift },
177 );
178 }
179
180 for_each_core_def(dfg, |instance, def| {
182 builder.observe(instance, core_def_callable(def));
183 });
184
185 let analysis = builder.finish();
188 let mut transparent = EntitySet::new();
189 for (id, adapter) in dfg.adapters.iter() {
190 if analysis.adapter_is_transparent(facts(types, adapter)) {
191 transparent.insert(id);
192 }
193 }
194 transparent
195}
196
197fn facts(types: &ComponentTypesBuilder, adapter: &super::Adapter) -> AdapterFacts {
198 AdapterFacts {
199 callee: adapter.lift_options.instance,
200 any_async: adapter.lift_options.async_
201 || adapter.lower_options.async_
202 || types[adapter.lift_ty].async_
203 || types[adapter.lower_ty].async_,
204 any_handle: types.func_contains_any_handle(adapter.lift_ty)
205 || types.func_contains_any_handle(adapter.lower_ty),
206 }
207}
208
209fn core_def_callable(def: &CoreDef) -> CoreCallable {
211 match def {
213 CoreDef::UnsafeIntrinsic(_, intrinsic) => match intrinsic {
214 UnsafeIntrinsic::ContextGetI32_0
215 | UnsafeIntrinsic::ContextSetI32_0
216 | UnsafeIntrinsic::ContextGetI32_1
217 | UnsafeIntrinsic::ContextSetI32_1 => CoreCallable::ContextAccess,
218
219 UnsafeIntrinsic::StoreDataAddress
221 | UnsafeIntrinsic::U8NativeLoad
222 | UnsafeIntrinsic::U8NativeStore
223 | UnsafeIntrinsic::U16NativeLoad
224 | UnsafeIntrinsic::U16NativeStore
225 | UnsafeIntrinsic::U32NativeLoad
226 | UnsafeIntrinsic::U32NativeStore
227 | UnsafeIntrinsic::U64NativeLoad
228 | UnsafeIntrinsic::U64NativeStore
229 | UnsafeIntrinsic::U8CheckedNativeLoad
230 | UnsafeIntrinsic::U8CheckedNativeStore
231 | UnsafeIntrinsic::U16CheckedNativeLoad
232 | UnsafeIntrinsic::U16CheckedNativeStore
233 | UnsafeIntrinsic::U32CheckedNativeLoad
234 | UnsafeIntrinsic::U32CheckedNativeStore
235 | UnsafeIntrinsic::U64CheckedNativeLoad
236 | UnsafeIntrinsic::U64CheckedNativeStore => CoreCallable::Inert,
237 },
238
239 CoreDef::Trampoline(_) | CoreDef::Adapter(_) => CoreCallable::Inert,
242
243 CoreDef::Export(_) | CoreDef::InstanceFlags(_) => CoreCallable::Inert,
246 }
247}
248
249fn trampoline_callable(
251 dfg: &ComponentDfg,
252 trampoline: &Trampoline,
253) -> (RuntimeComponentInstanceIndex, CoreCallable) {
254 use Trampoline::*;
255
256 match trampoline {
258 LowerImport { options, .. } => (dfg.options[*options].instance, CoreCallable::HostImport),
259
260 ResourceNew { instance, .. }
261 | ResourceRep { instance, .. }
262 | ResourceDrop { instance, .. }
263 | BackpressureInc { instance }
264 | BackpressureDec { instance }
265 | TaskReturn { instance, .. }
266 | TaskCancel { instance }
267 | WaitableSetNew { instance }
268 | WaitableSetWait { instance, .. }
269 | WaitableSetPoll { instance, .. }
270 | WaitableSetDrop { instance }
271 | WaitableJoin { instance }
272 | SubtaskDrop { instance }
273 | SubtaskCancel { instance, .. }
274 | StreamNew { instance, .. }
275 | StreamRead { instance, .. }
276 | StreamWrite { instance, .. }
277 | StreamCancelRead { instance, .. }
278 | StreamCancelWrite { instance, .. }
279 | StreamDropReadable { instance, .. }
280 | StreamDropWritable { instance, .. }
281 | FutureNew { instance, .. }
282 | FutureRead { instance, .. }
283 | FutureWrite { instance, .. }
284 | FutureCancelRead { instance, .. }
285 | FutureCancelWrite { instance, .. }
286 | FutureDropReadable { instance, .. }
287 | FutureDropWritable { instance, .. }
288 | ErrorContextNew { instance, .. }
289 | ErrorContextDebugMessage { instance, .. }
290 | ErrorContextDrop { instance, .. }
291 | ThreadIndex { instance }
292 | ThreadNewIndirect { instance, .. }
293 | ThreadResumeLater { instance }
294 | ThreadSuspend { instance, .. }
295 | ThreadYield { instance, .. }
296 | ThreadSuspendThenResume { instance, .. }
297 | ThreadYieldThenResume { instance, .. }
298 | ThreadSuspendThenPromote { instance, .. }
299 | ThreadYieldThenPromote { instance, .. } => (*instance, CoreCallable::ThreadStateBuiltin),
300
301 Transcoder { .. }
305 | ResourceTransferOwn
306 | ResourceTransferBorrow
307 | ResourceTransferBorrowToOwner
308 | PrepareCall { .. }
309 | StartCall { .. }
310 | FutureTransfer
311 | StreamTransfer
312 | ErrorContextTransfer
313 | Trap(_)
314 | HostcallFuel
315 | EnterSyncCall
316 | ExitSyncCall => {
317 unreachable!("these trampolines do not exist yet")
318 }
319 }
320}
321
322fn for_each_core_def(
325 dfg: &ComponentDfg,
326 mut f: impl FnMut(RuntimeComponentInstanceIndex, &CoreDef),
327) {
328 for effect in dfg.side_effects.iter() {
329 let SideEffect::Instance(id, instance) = effect else {
330 continue;
331 };
332 match &dfg.instances[*id] {
333 Instance::Static(_, args) => {
334 for def in args.iter() {
335 f(*instance, def);
336 }
337 }
338 Instance::Import(_, args) => {
339 for (_, defs) in args {
340 for (_, def) in defs {
341 f(*instance, def);
342 }
343 }
344 }
345 }
346 }
347
348 for (_, adapter) in dfg.adapters.iter() {
349 f(adapter.lift_options.instance, &adapter.func);
350 for options in [&adapter.lift_options, &adapter.lower_options] {
351 for def in options
352 .callback
353 .iter()
354 .chain(options.post_return.iter())
355 .chain(match &options.data_model {
356 DataModel::LinearMemory { realloc, .. } => realloc.iter(),
357 DataModel::Gc {} => None.iter(),
358 })
359 {
360 f(options.instance, def);
361 }
362 }
363 }
364
365 for (_, options) in dfg.options.iter() {
366 if let Some(callback) = options.callback {
367 f(options.instance, &dfg.callbacks[callback]);
368 }
369 if let Some(post_return) = options.post_return {
370 f(options.instance, &dfg.post_returns[post_return]);
371 }
372 if let CanonicalOptionsDataModel::LinearMemory {
373 realloc: Some(realloc),
374 ..
375 } = &options.data_model
376 {
377 f(options.instance, &dfg.reallocs[*realloc]);
378 }
379 }
380
381 for (_, resource) in dfg.resources.iter() {
382 if let Some(dtor) = &resource.dtor {
383 f(resource.instance, dtor);
384 }
385 }
386
387 for (_, (export, _)) in dfg.exports.iter() {
388 for_each_export_core_def(dfg, export, &mut f);
389 }
390}
391
392fn for_each_export_core_def(
393 dfg: &ComponentDfg,
394 export: &Export,
395 f: &mut impl FnMut(RuntimeComponentInstanceIndex, &CoreDef),
396) {
397 match export {
398 Export::LiftedFunction { func, options, .. } => {
399 f(dfg.options[*options].instance, func);
400 }
401 Export::Instance { exports, .. } => {
402 for (_, (export, _)) in exports.iter() {
403 for_each_export_core_def(dfg, export, f);
404 }
405 }
406 Export::ModuleStatic { .. } | Export::ModuleImport { .. } | Export::Type(_) => {}
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 fn instance(i: u32) -> RuntimeComponentInstanceIndex {
415 RuntimeComponentInstanceIndex::from_u32(i)
416 }
417
418 fn analyze(observations: &[(u32, CoreCallable)]) -> ThreadTransparency {
421 let mut builder = ThreadTransparencyBuilder::default();
422 for (i, callable) in observations {
423 builder.observe(instance(*i), *callable);
424 }
425 builder.finish()
426 }
427
428 fn facts(callee: u32) -> AdapterFacts {
431 AdapterFacts {
432 callee: instance(callee),
433 any_async: false,
434 any_handle: false,
435 }
436 }
437
438 #[test]
439 fn clean_instance_is_transparent() {
440 let t = analyze(&[]);
441 assert!(t.adapter_is_transparent(facts(0)));
442 }
443
444 #[test]
445 fn context_access_makes_instance_opaque() {
446 let t = analyze(&[(0, CoreCallable::ContextAccess)]);
447 assert!(!t.adapter_is_transparent(facts(0)));
448 }
449
450 #[test]
451 fn thread_state_builtin_makes_instance_opaque() {
452 let t = analyze(&[(0, CoreCallable::ThreadStateBuiltin)]);
453 assert!(!t.adapter_is_transparent(facts(0)));
454 }
455
456 #[test]
457 fn host_import_lowering_makes_instance_opaque() {
458 let t = analyze(&[(0, CoreCallable::HostImport)]);
459 assert!(!t.adapter_is_transparent(facts(0)));
460 }
461
462 #[test]
463 fn inert_callable_leaves_instance_transparent() {
464 let t = analyze(&[(0, CoreCallable::Inert), (0, CoreCallable::Inert)]);
465 assert!(t.adapter_is_transparent(facts(0)));
466 }
467
468 #[test]
472 fn sync_adapter_lowering_leaves_instance_transparent() {
473 let t = analyze(&[(0, CoreCallable::Adapter { async_lift: false })]);
474 assert!(t.adapter_is_transparent(facts(0)));
475 }
476
477 #[test]
481 fn async_lift_makes_the_lowering_instance_opaque() {
482 let t = analyze(&[(0, CoreCallable::Adapter { async_lift: true })]);
483 assert!(!t.adapter_is_transparent(facts(0)));
484 }
485
486 #[test]
489 fn only_the_callee_side_is_judged() {
490 let t = analyze(&[(0, CoreCallable::ContextAccess)]);
491 assert!(t.adapter_is_transparent(facts(1)));
492 }
493
494 #[test]
498 fn opacity_does_not_propagate_outward() {
499 let t = analyze(&[(2, CoreCallable::ContextAccess)]);
500 assert!(t.adapter_is_transparent(facts(1)), "outer -> mid");
501 assert!(!t.adapter_is_transparent(facts(2)), "mid -> inner");
502 }
503
504 #[test]
507 fn opacity_does_not_propagate_inward() {
508 let t = analyze(&[(0, CoreCallable::ContextAccess)]);
509 assert!(t.adapter_is_transparent(facts(1)), "outer -> mid");
510 assert!(t.adapter_is_transparent(facts(2)), "mid -> inner");
511 }
512
513 #[test]
515 fn siblings_are_judged_independently() {
516 let t = analyze(&[(0, CoreCallable::ContextAccess)]);
517 assert!(!t.adapter_is_transparent(facts(0)));
518 assert!(t.adapter_is_transparent(facts(1)));
519 }
520
521 #[test]
522 fn async_signature_is_never_transparent() {
523 let t = analyze(&[]);
524 assert!(!t.adapter_is_transparent(AdapterFacts {
525 any_async: true,
526 ..facts(0)
527 }));
528 }
529
530 #[test]
531 fn handle_in_signature_is_never_transparent() {
532 let t = analyze(&[]);
533 assert!(!t.adapter_is_transparent(AdapterFacts {
534 any_handle: true,
535 ..facts(0)
536 }));
537 }
538
539 #[test]
542 fn observations_are_monotone_and_order_independent() {
543 let dirty_first = analyze(&[
544 (0, CoreCallable::ContextAccess),
545 (0, CoreCallable::Inert),
546 (0, CoreCallable::Adapter { async_lift: false }),
547 ]);
548 let dirty_last = analyze(&[
549 (0, CoreCallable::Adapter { async_lift: false }),
550 (0, CoreCallable::Inert),
551 (0, CoreCallable::ContextAccess),
552 ]);
553 assert!(!dirty_first.adapter_is_transparent(facts(0)));
554 assert!(!dirty_last.adapter_is_transparent(facts(0)));
555 }
556}