use crate::component::dfg::{
AdapterId, CanonicalOptionsDataModel, ComponentDfg, CoreDef, Export, Instance, SideEffect,
Trampoline,
};
use crate::component::{
ComponentTypesBuilder, DataModel, RuntimeComponentInstanceIndex, UnsafeIntrinsic,
};
use cranelift_entity::EntitySet;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum CoreCallable {
HostImport,
Adapter {
async_lift: bool,
},
ContextAccess,
ThreadStateBuiltin,
Inert,
}
impl CoreCallable {
fn may_touch_thread_state(self) -> bool {
match self {
CoreCallable::HostImport => true,
CoreCallable::Adapter { async_lift } => async_lift,
CoreCallable::ContextAccess | CoreCallable::ThreadStateBuiltin => true,
CoreCallable::Inert => false,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct AdapterFacts {
callee: RuntimeComponentInstanceIndex,
any_async: bool,
any_handle: bool,
}
#[derive(Default)]
struct ThreadTransparencyBuilder {
opaque: EntitySet<RuntimeComponentInstanceIndex>,
}
impl ThreadTransparencyBuilder {
fn observe(&mut self, instance: RuntimeComponentInstanceIndex, callable: CoreCallable) {
if callable.may_touch_thread_state() {
self.opaque.insert(instance);
}
}
fn finish(self) -> ThreadTransparency {
ThreadTransparency {
opaque: self.opaque,
}
}
}
struct ThreadTransparency {
opaque: EntitySet<RuntimeComponentInstanceIndex>,
}
impl ThreadTransparency {
fn adapter_is_transparent(&self, facts: AdapterFacts) -> bool {
if facts.any_async {
return false;
}
if facts.any_handle {
return false;
}
!self.opaque.contains(facts.callee)
}
}
pub fn transparent_adapters(
dfg: &ComponentDfg,
types: &ComponentTypesBuilder,
) -> EntitySet<AdapterId> {
let mut builder = ThreadTransparencyBuilder::default();
for (_, (_, trampoline)) in dfg.trampolines.iter() {
let (instance, callable) = trampoline_callable(dfg, trampoline);
builder.observe(instance, callable);
}
for (_, adapter) in dfg.adapters.iter() {
let async_lift = types[adapter.lift_ty].async_;
builder.observe(
adapter.lower_options.instance,
CoreCallable::Adapter { async_lift },
);
}
for_each_core_def(dfg, |instance, def| {
builder.observe(instance, core_def_callable(def));
});
let analysis = builder.finish();
let mut transparent = EntitySet::new();
for (id, adapter) in dfg.adapters.iter() {
if analysis.adapter_is_transparent(facts(types, adapter)) {
transparent.insert(id);
}
}
transparent
}
fn facts(types: &ComponentTypesBuilder, adapter: &super::Adapter) -> AdapterFacts {
AdapterFacts {
callee: adapter.lift_options.instance,
any_async: adapter.lift_options.async_
|| adapter.lower_options.async_
|| types[adapter.lift_ty].async_
|| types[adapter.lower_ty].async_,
any_handle: types.func_contains_any_handle(adapter.lift_ty)
|| types.func_contains_any_handle(adapter.lower_ty),
}
}
fn core_def_callable(def: &CoreDef) -> CoreCallable {
match def {
CoreDef::UnsafeIntrinsic(_, intrinsic) => match intrinsic {
UnsafeIntrinsic::ContextGetI32_0
| UnsafeIntrinsic::ContextSetI32_0
| UnsafeIntrinsic::ContextGetI32_1
| UnsafeIntrinsic::ContextSetI32_1 => CoreCallable::ContextAccess,
UnsafeIntrinsic::StoreDataAddress
| UnsafeIntrinsic::U8NativeLoad
| UnsafeIntrinsic::U8NativeStore
| UnsafeIntrinsic::U16NativeLoad
| UnsafeIntrinsic::U16NativeStore
| UnsafeIntrinsic::U32NativeLoad
| UnsafeIntrinsic::U32NativeStore
| UnsafeIntrinsic::U64NativeLoad
| UnsafeIntrinsic::U64NativeStore
| UnsafeIntrinsic::U8CheckedNativeLoad
| UnsafeIntrinsic::U8CheckedNativeStore
| UnsafeIntrinsic::U16CheckedNativeLoad
| UnsafeIntrinsic::U16CheckedNativeStore
| UnsafeIntrinsic::U32CheckedNativeLoad
| UnsafeIntrinsic::U32CheckedNativeStore
| UnsafeIntrinsic::U64CheckedNativeLoad
| UnsafeIntrinsic::U64CheckedNativeStore => CoreCallable::Inert,
},
CoreDef::Trampoline(_) | CoreDef::Adapter(_) => CoreCallable::Inert,
CoreDef::Export(_) | CoreDef::InstanceFlags(_) => CoreCallable::Inert,
}
}
fn trampoline_callable(
dfg: &ComponentDfg,
trampoline: &Trampoline,
) -> (RuntimeComponentInstanceIndex, CoreCallable) {
use Trampoline::*;
match trampoline {
LowerImport { options, .. } => (dfg.options[*options].instance, CoreCallable::HostImport),
ResourceNew { instance, .. }
| ResourceRep { instance, .. }
| ResourceDrop { instance, .. }
| BackpressureInc { instance }
| BackpressureDec { instance }
| TaskReturn { instance, .. }
| TaskCancel { instance }
| WaitableSetNew { instance }
| WaitableSetWait { instance, .. }
| WaitableSetPoll { instance, .. }
| WaitableSetDrop { instance }
| WaitableJoin { instance }
| SubtaskDrop { instance }
| SubtaskCancel { instance, .. }
| StreamNew { instance, .. }
| StreamRead { instance, .. }
| StreamWrite { instance, .. }
| StreamCancelRead { instance, .. }
| StreamCancelWrite { instance, .. }
| StreamDropReadable { instance, .. }
| StreamDropWritable { instance, .. }
| FutureNew { instance, .. }
| FutureRead { instance, .. }
| FutureWrite { instance, .. }
| FutureCancelRead { instance, .. }
| FutureCancelWrite { instance, .. }
| FutureDropReadable { instance, .. }
| FutureDropWritable { instance, .. }
| ErrorContextNew { instance, .. }
| ErrorContextDebugMessage { instance, .. }
| ErrorContextDrop { instance, .. }
| ThreadIndex { instance }
| ThreadNewIndirect { instance, .. }
| ThreadResumeLater { instance }
| ThreadSuspend { instance, .. }
| ThreadYield { instance, .. }
| ThreadSuspendThenResume { instance, .. }
| ThreadYieldThenResume { instance, .. }
| ThreadSuspendThenPromote { instance, .. }
| ThreadYieldThenPromote { instance, .. } => (*instance, CoreCallable::ThreadStateBuiltin),
Transcoder { .. }
| ResourceTransferOwn
| ResourceTransferBorrow
| PrepareCall { .. }
| StartCall { .. }
| FutureTransfer
| StreamTransfer
| ErrorContextTransfer
| Trap(_)
| EnterSyncCall
| ExitSyncCall => {
unreachable!("these trampolines do not exist yet")
}
}
}
fn for_each_core_def(
dfg: &ComponentDfg,
mut f: impl FnMut(RuntimeComponentInstanceIndex, &CoreDef),
) {
for effect in dfg.side_effects.iter() {
let SideEffect::Instance(id, instance) = effect else {
continue;
};
match &dfg.instances[*id] {
Instance::Static(_, args) => {
for def in args.iter() {
f(*instance, def);
}
}
Instance::Import(_, args) => {
for (_, defs) in args {
for (_, def) in defs {
f(*instance, def);
}
}
}
}
}
for (_, adapter) in dfg.adapters.iter() {
f(adapter.lift_options.instance, &adapter.func);
for options in [&adapter.lift_options, &adapter.lower_options] {
for def in options
.callback
.iter()
.chain(options.post_return.iter())
.chain(match &options.data_model {
DataModel::LinearMemory { realloc, .. } => realloc.iter(),
DataModel::Gc {} => None.iter(),
})
{
f(options.instance, def);
}
}
}
for (_, options) in dfg.options.iter() {
if let Some(callback) = options.callback {
f(options.instance, &dfg.callbacks[callback]);
}
if let Some(post_return) = options.post_return {
f(options.instance, &dfg.post_returns[post_return]);
}
if let CanonicalOptionsDataModel::LinearMemory {
realloc: Some(realloc),
..
} = &options.data_model
{
f(options.instance, &dfg.reallocs[*realloc]);
}
}
for (_, resource) in dfg.resources.iter() {
if let Some(dtor) = &resource.dtor {
f(resource.instance, dtor);
}
}
for (_, (export, _)) in dfg.exports.iter() {
for_each_export_core_def(dfg, export, &mut f);
}
}
fn for_each_export_core_def(
dfg: &ComponentDfg,
export: &Export,
f: &mut impl FnMut(RuntimeComponentInstanceIndex, &CoreDef),
) {
match export {
Export::LiftedFunction { func, options, .. } => {
f(dfg.options[*options].instance, func);
}
Export::Instance { exports, .. } => {
for (_, (export, _)) in exports.iter() {
for_each_export_core_def(dfg, export, f);
}
}
Export::ModuleStatic { .. } | Export::ModuleImport { .. } | Export::Type(_) => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn instance(i: u32) -> RuntimeComponentInstanceIndex {
RuntimeComponentInstanceIndex::from_u32(i)
}
fn analyze(observations: &[(u32, CoreCallable)]) -> ThreadTransparency {
let mut builder = ThreadTransparencyBuilder::default();
for (i, callable) in observations {
builder.observe(instance(*i), *callable);
}
builder.finish()
}
fn facts(callee: u32) -> AdapterFacts {
AdapterFacts {
callee: instance(callee),
any_async: false,
any_handle: false,
}
}
#[test]
fn clean_instance_is_transparent() {
let t = analyze(&[]);
assert!(t.adapter_is_transparent(facts(0)));
}
#[test]
fn context_access_makes_instance_opaque() {
let t = analyze(&[(0, CoreCallable::ContextAccess)]);
assert!(!t.adapter_is_transparent(facts(0)));
}
#[test]
fn thread_state_builtin_makes_instance_opaque() {
let t = analyze(&[(0, CoreCallable::ThreadStateBuiltin)]);
assert!(!t.adapter_is_transparent(facts(0)));
}
#[test]
fn host_import_lowering_makes_instance_opaque() {
let t = analyze(&[(0, CoreCallable::HostImport)]);
assert!(!t.adapter_is_transparent(facts(0)));
}
#[test]
fn inert_callable_leaves_instance_transparent() {
let t = analyze(&[(0, CoreCallable::Inert), (0, CoreCallable::Inert)]);
assert!(t.adapter_is_transparent(facts(0)));
}
#[test]
fn sync_adapter_lowering_leaves_instance_transparent() {
let t = analyze(&[(0, CoreCallable::Adapter { async_lift: false })]);
assert!(t.adapter_is_transparent(facts(0)));
}
#[test]
fn async_lift_makes_the_lowering_instance_opaque() {
let t = analyze(&[(0, CoreCallable::Adapter { async_lift: true })]);
assert!(!t.adapter_is_transparent(facts(0)));
}
#[test]
fn only_the_callee_side_is_judged() {
let t = analyze(&[(0, CoreCallable::ContextAccess)]);
assert!(t.adapter_is_transparent(facts(1)));
}
#[test]
fn opacity_does_not_propagate_outward() {
let t = analyze(&[(2, CoreCallable::ContextAccess)]);
assert!(t.adapter_is_transparent(facts(1)), "outer -> mid");
assert!(!t.adapter_is_transparent(facts(2)), "mid -> inner");
}
#[test]
fn opacity_does_not_propagate_inward() {
let t = analyze(&[(0, CoreCallable::ContextAccess)]);
assert!(t.adapter_is_transparent(facts(1)), "outer -> mid");
assert!(t.adapter_is_transparent(facts(2)), "mid -> inner");
}
#[test]
fn siblings_are_judged_independently() {
let t = analyze(&[(0, CoreCallable::ContextAccess)]);
assert!(!t.adapter_is_transparent(facts(0)));
assert!(t.adapter_is_transparent(facts(1)));
}
#[test]
fn async_signature_is_never_transparent() {
let t = analyze(&[]);
assert!(!t.adapter_is_transparent(AdapterFacts {
any_async: true,
..facts(0)
}));
}
#[test]
fn handle_in_signature_is_never_transparent() {
let t = analyze(&[]);
assert!(!t.adapter_is_transparent(AdapterFacts {
any_handle: true,
..facts(0)
}));
}
#[test]
fn observations_are_monotone_and_order_independent() {
let dirty_first = analyze(&[
(0, CoreCallable::ContextAccess),
(0, CoreCallable::Inert),
(0, CoreCallable::Adapter { async_lift: false }),
]);
let dirty_last = analyze(&[
(0, CoreCallable::Adapter { async_lift: false }),
(0, CoreCallable::Inert),
(0, CoreCallable::ContextAccess),
]);
assert!(!dirty_first.adapter_is_transparent(facts(0)));
assert!(!dirty_last.adapter_is_transparent(facts(0)));
}
}