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 | PrepareCall { .. }
308 | SyncStartCall { .. }
309 | AsyncStartCall { .. }
310 | FutureTransfer
311 | StreamTransfer
312 | ErrorContextTransfer
313 | Trap(_)
314 | EnterSyncCall
315 | ExitSyncCall => {
316 unreachable!("these trampolines do not exist yet")
317 }
318 }
319}
320
321fn for_each_core_def(
324 dfg: &ComponentDfg,
325 mut f: impl FnMut(RuntimeComponentInstanceIndex, &CoreDef),
326) {
327 for effect in dfg.side_effects.iter() {
328 let SideEffect::Instance(id, instance) = effect else {
329 continue;
330 };
331 match &dfg.instances[*id] {
332 Instance::Static(_, args) => {
333 for def in args.iter() {
334 f(*instance, def);
335 }
336 }
337 Instance::Import(_, args) => {
338 for (_, defs) in args {
339 for (_, def) in defs {
340 f(*instance, def);
341 }
342 }
343 }
344 }
345 }
346
347 for (_, adapter) in dfg.adapters.iter() {
348 f(adapter.lift_options.instance, &adapter.func);
349 for options in [&adapter.lift_options, &adapter.lower_options] {
350 for def in options
351 .callback
352 .iter()
353 .chain(options.post_return.iter())
354 .chain(match &options.data_model {
355 DataModel::LinearMemory { realloc, .. } => realloc.iter(),
356 DataModel::Gc {} => None.iter(),
357 })
358 {
359 f(options.instance, def);
360 }
361 }
362 }
363
364 for (_, options) in dfg.options.iter() {
365 if let Some(callback) = options.callback {
366 f(options.instance, &dfg.callbacks[callback]);
367 }
368 if let Some(post_return) = options.post_return {
369 f(options.instance, &dfg.post_returns[post_return]);
370 }
371 if let CanonicalOptionsDataModel::LinearMemory {
372 realloc: Some(realloc),
373 ..
374 } = &options.data_model
375 {
376 f(options.instance, &dfg.reallocs[*realloc]);
377 }
378 }
379
380 for (_, resource) in dfg.resources.iter() {
381 if let Some(dtor) = &resource.dtor {
382 f(resource.instance, dtor);
383 }
384 }
385
386 for (_, (export, _)) in dfg.exports.iter() {
387 for_each_export_core_def(dfg, export, &mut f);
388 }
389}
390
391fn for_each_export_core_def(
392 dfg: &ComponentDfg,
393 export: &Export,
394 f: &mut impl FnMut(RuntimeComponentInstanceIndex, &CoreDef),
395) {
396 match export {
397 Export::LiftedFunction { func, options, .. } => {
398 f(dfg.options[*options].instance, func);
399 }
400 Export::Instance { exports, .. } => {
401 for (_, (export, _)) in exports.iter() {
402 for_each_export_core_def(dfg, export, f);
403 }
404 }
405 Export::ModuleStatic { .. } | Export::ModuleImport { .. } | Export::Type(_) => {}
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 fn instance(i: u32) -> RuntimeComponentInstanceIndex {
414 RuntimeComponentInstanceIndex::from_u32(i)
415 }
416
417 fn analyze(observations: &[(u32, CoreCallable)]) -> ThreadTransparency {
420 let mut builder = ThreadTransparencyBuilder::default();
421 for (i, callable) in observations {
422 builder.observe(instance(*i), *callable);
423 }
424 builder.finish()
425 }
426
427 fn facts(callee: u32) -> AdapterFacts {
430 AdapterFacts {
431 callee: instance(callee),
432 any_async: false,
433 any_handle: false,
434 }
435 }
436
437 #[test]
438 fn clean_instance_is_transparent() {
439 let t = analyze(&[]);
440 assert!(t.adapter_is_transparent(facts(0)));
441 }
442
443 #[test]
444 fn context_access_makes_instance_opaque() {
445 let t = analyze(&[(0, CoreCallable::ContextAccess)]);
446 assert!(!t.adapter_is_transparent(facts(0)));
447 }
448
449 #[test]
450 fn thread_state_builtin_makes_instance_opaque() {
451 let t = analyze(&[(0, CoreCallable::ThreadStateBuiltin)]);
452 assert!(!t.adapter_is_transparent(facts(0)));
453 }
454
455 #[test]
456 fn host_import_lowering_makes_instance_opaque() {
457 let t = analyze(&[(0, CoreCallable::HostImport)]);
458 assert!(!t.adapter_is_transparent(facts(0)));
459 }
460
461 #[test]
462 fn inert_callable_leaves_instance_transparent() {
463 let t = analyze(&[(0, CoreCallable::Inert), (0, CoreCallable::Inert)]);
464 assert!(t.adapter_is_transparent(facts(0)));
465 }
466
467 #[test]
471 fn sync_adapter_lowering_leaves_instance_transparent() {
472 let t = analyze(&[(0, CoreCallable::Adapter { async_lift: false })]);
473 assert!(t.adapter_is_transparent(facts(0)));
474 }
475
476 #[test]
480 fn async_lift_makes_the_lowering_instance_opaque() {
481 let t = analyze(&[(0, CoreCallable::Adapter { async_lift: true })]);
482 assert!(!t.adapter_is_transparent(facts(0)));
483 }
484
485 #[test]
488 fn only_the_callee_side_is_judged() {
489 let t = analyze(&[(0, CoreCallable::ContextAccess)]);
490 assert!(t.adapter_is_transparent(facts(1)));
491 }
492
493 #[test]
497 fn opacity_does_not_propagate_outward() {
498 let t = analyze(&[(2, CoreCallable::ContextAccess)]);
499 assert!(t.adapter_is_transparent(facts(1)), "outer -> mid");
500 assert!(!t.adapter_is_transparent(facts(2)), "mid -> inner");
501 }
502
503 #[test]
506 fn opacity_does_not_propagate_inward() {
507 let t = analyze(&[(0, CoreCallable::ContextAccess)]);
508 assert!(t.adapter_is_transparent(facts(1)), "outer -> mid");
509 assert!(t.adapter_is_transparent(facts(2)), "mid -> inner");
510 }
511
512 #[test]
514 fn siblings_are_judged_independently() {
515 let t = analyze(&[(0, CoreCallable::ContextAccess)]);
516 assert!(!t.adapter_is_transparent(facts(0)));
517 assert!(t.adapter_is_transparent(facts(1)));
518 }
519
520 #[test]
521 fn async_signature_is_never_transparent() {
522 let t = analyze(&[]);
523 assert!(!t.adapter_is_transparent(AdapterFacts {
524 any_async: true,
525 ..facts(0)
526 }));
527 }
528
529 #[test]
530 fn handle_in_signature_is_never_transparent() {
531 let t = analyze(&[]);
532 assert!(!t.adapter_is_transparent(AdapterFacts {
533 any_handle: true,
534 ..facts(0)
535 }));
536 }
537
538 #[test]
541 fn observations_are_monotone_and_order_independent() {
542 let dirty_first = analyze(&[
543 (0, CoreCallable::ContextAccess),
544 (0, CoreCallable::Inert),
545 (0, CoreCallable::Adapter { async_lift: false }),
546 ]);
547 let dirty_last = analyze(&[
548 (0, CoreCallable::Adapter { async_lift: false }),
549 (0, CoreCallable::Inert),
550 (0, CoreCallable::ContextAccess),
551 ]);
552 assert!(!dirty_first.adapter_is_transparent(facts(0)));
553 assert!(!dirty_last.adapter_is_transparent(facts(0)));
554 }
555}