use super::Module;
use crate::{
Result,
arch::NativeArch,
custom_error,
elf::SymbolLookup,
input::ModuleSourceId,
memory::VmAddr,
relocation::RelocationArch,
runtime::DomainId,
sync::{Arc, AtomicU8, AtomicUsize, Ordering, Weak, arc_unsize},
tls::TlsResolver,
};
use alloc::vec::Vec;
use core::{fmt, ops::Deref};
use spin::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
const UNINITIALIZED: u8 = 0;
const INITIALIZING: u8 = 1;
const INITIALIZED: u8 = 2;
const FAILED: u8 = 3;
const FINALIZED: u8 = 4;
static NEXT_INSTANCE: AtomicUsize = AtomicUsize::new(1);
#[inline]
fn next_instance() -> usize {
NEXT_INSTANCE
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
value.checked_add(1)
})
.expect("module instance identity space is exhausted")
}
struct InitializationGuard<'a>(&'a AtomicU8);
impl Drop for InitializationGuard<'_> {
fn drop(&mut self) {
self.0.store(FAILED, Ordering::Release);
}
}
#[inline]
pub(super) fn lookup_symbol<Arch, Tls>(
module: &dyn Module<Arch, Tls>,
lookup: &mut SymbolLookup<'_>,
) -> Result<Option<VmAddr>>
where
Arch: RelocationArch,
Tls: TlsResolver<Arch>,
{
let Some(symbol) = module.exports().lookup(lookup) else {
return Ok(None);
};
if !symbol.is_exported() {
return Ok(None);
}
module.resolve_symbol(symbol).map(Some)
}
pub struct ModuleState {
id: ModuleInstanceId,
domain: DomainId,
phase: AtomicU8,
effects: Mutex<ModuleEffects>,
}
#[derive(Default)]
struct ModuleEffects {
bindings: Vec<ModuleInstanceId>,
pins: Vec<ModuleInstanceId>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ModuleInstanceId {
source: ModuleSourceId,
instance: usize,
}
impl ModuleInstanceId {
#[inline]
fn new(source: ModuleSourceId) -> Self {
Self {
source,
instance: next_instance(),
}
}
#[inline]
pub const fn source_id(self) -> ModuleSourceId {
self.source
}
}
impl ModuleState {
#[inline]
pub fn new(source: ModuleSourceId, domain: DomainId) -> Self {
Self {
id: ModuleInstanceId::new(source),
domain,
phase: AtomicU8::new(UNINITIALIZED),
effects: Mutex::new(ModuleEffects::default()),
}
}
#[inline]
pub fn initialized(source: ModuleSourceId, domain: DomainId) -> Self {
Self {
id: ModuleInstanceId::new(source),
domain,
phase: AtomicU8::new(INITIALIZED),
effects: Mutex::new(ModuleEffects::default()),
}
}
#[inline]
pub const fn instance_id(&self) -> ModuleInstanceId {
self.id
}
#[inline]
pub const fn domain_id(&self) -> DomainId {
self.domain
}
#[inline]
pub(super) fn set_domain(&mut self, domain: DomainId) {
self.domain = domain;
}
#[inline]
pub(crate) fn with_effects<T>(
&self,
f: impl FnOnce(&[ModuleInstanceId], &[ModuleInstanceId]) -> T,
) -> T {
let effects = self.effects.lock();
f(&effects.bindings, &effects.pins)
}
pub(crate) fn install_effects(
&self,
bindings: impl IntoIterator<Item = ModuleInstanceId>,
pins: impl IntoIterator<Item = ModuleInstanceId>,
) {
let mut effects = self.effects.lock();
for binding in bindings {
if binding != self.id && !effects.bindings.contains(&binding) {
effects.bindings.push(binding);
}
}
for pin in pins {
if !effects.pins.contains(&pin) {
effects.pins.push(pin);
}
}
}
#[inline]
pub fn is_initialized(&self) -> bool {
self.phase.load(Ordering::Acquire) == INITIALIZED
}
pub fn initialize(&self, initialize: impl FnOnce() -> Result<()>) -> Result<()> {
let mut phase = self.phase.load(Ordering::Acquire);
loop {
match phase {
INITIALIZING | INITIALIZED => return Ok(()),
FAILED => return Err(custom_error("cannot initialize a failed module")),
FINALIZED => return Err(custom_error("cannot initialize a finalized module")),
_ => {}
}
match self.phase.compare_exchange_weak(
phase,
INITIALIZING,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => {
let guard = InitializationGuard(&self.phase);
let result = initialize();
self.phase.store(
if result.is_ok() { INITIALIZED } else { FAILED },
Ordering::Release,
);
core::mem::forget(guard);
return result;
}
Err(current) => phase = current,
}
}
}
pub fn finalize(&self, finalize: impl FnOnce() -> Result<()>) -> Result<()> {
let mut phase = self.phase.load(Ordering::Acquire);
loop {
match phase {
INITIALIZED | FAILED => {}
_ => return Ok(()),
}
match self.phase.compare_exchange_weak(
phase,
FINALIZED,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return finalize(),
Err(current) => phase = current,
}
}
}
}
impl Default for ModuleState {
#[inline]
fn default() -> Self {
Self::new(ModuleSourceId::fresh(), DomainId::PROCESS)
}
}
pub struct ModuleHandle<Arch: RelocationArch = NativeArch, Tls: TlsResolver<Arch> = ()> {
module: Arc<dyn Module<Arch, Tls>>,
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch>> Clone for ModuleHandle<Arch, Tls> {
#[inline]
fn clone(&self) -> Self {
Self {
module: Arc::clone(&self.module),
}
}
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch> + 'static> ModuleHandle<Arch, Tls> {
#[inline]
pub fn new<M>(module: M) -> Self
where
M: Module<Arch, Tls> + 'static,
{
Self::from_shared(arc_unsize!(Arc::new(module) => dyn Module<Arch, Tls>))
}
#[inline]
pub fn from_shared(module: Arc<dyn Module<Arch, Tls>>) -> Self {
Self { module }
}
#[inline]
pub(crate) fn downgrade(&self) -> Weak<dyn Module<Arch, Tls>> {
Arc::downgrade(&self.module)
}
#[inline]
pub fn source_id(&self) -> ModuleSourceId {
self.module.state().instance_id().source_id()
}
#[inline]
pub fn domain_id(&self) -> DomainId {
self.module.state().domain_id()
}
#[inline]
pub fn initialize(&self) -> Result<()> {
let module = &*self.module;
module.state().initialize(|| module.initialize())
}
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch> + 'static> Deref for ModuleHandle<Arch, Tls> {
type Target = dyn Module<Arch, Tls>;
#[inline]
fn deref(&self) -> &Self::Target {
&*self.module
}
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch> + 'static> AsRef<dyn Module<Arch, Tls>>
for ModuleHandle<Arch, Tls>
{
#[inline]
fn as_ref(&self) -> &(dyn Module<Arch, Tls> + 'static) {
&*self.module
}
}
pub struct ModuleScope<Arch: RelocationArch = NativeArch, Tls: TlsResolver<Arch> = ()> {
modules: Arc<Vec<ModuleHandle<Arch, Tls>>>,
domain: DomainId,
}
pub struct LocalScope<Arch: RelocationArch = NativeArch, Tls: TlsResolver<Arch> = ()> {
groups: Arc<[ModuleScope<Arch, Tls>]>,
lazy: ModuleScope<Arch, Tls>,
}
pub(crate) struct WeakLocalScope<Arch: RelocationArch = NativeArch, Tls: TlsResolver<Arch> = ()> {
local: Weak<Vec<ModuleHandle<Arch, Tls>>>,
global: Option<Weak<GlobalScopeInner<Arch, Tls>>>,
domain: DomainId,
}
pub struct GlobalScope<Arch: RelocationArch = NativeArch, Tls: TlsResolver<Arch> = ()> {
inner: Arc<GlobalScopeInner<Arch, Tls>>,
}
struct GlobalScopeInner<Arch: RelocationArch, Tls: TlsResolver<Arch>> {
modules: RwLock<ModuleScope<Arch, Tls>>,
domain: DomainId,
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch>> Clone for ModuleScope<Arch, Tls> {
#[inline]
fn clone(&self) -> Self {
Self {
modules: Arc::clone(&self.modules),
domain: self.domain,
}
}
}
impl<Arch, Tls> fmt::Debug for ModuleScope<Arch, Tls>
where
Arch: RelocationArch,
Tls: TlsResolver<Arch>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(self.modules.iter().map(|module| module.name()))
.finish()
}
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch>> Deref for ModuleScope<Arch, Tls> {
type Target = [ModuleHandle<Arch, Tls>];
#[inline]
fn deref(&self) -> &Self::Target {
&self.modules
}
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch>> ModuleScope<Arch, Tls> {
#[inline]
pub fn new(domain: DomainId) -> Self {
Self {
modules: Arc::new(Vec::new()),
domain,
}
}
#[inline]
pub const fn domain_id(&self) -> DomainId {
self.domain
}
pub fn check_domain(&self, expected: DomainId) -> Result<()> {
expected.ensure(self.domain)?;
for module in self.modules.iter() {
expected.ensure(module.domain_id())?;
}
Ok(())
}
pub fn push(&mut self, module: ModuleHandle<Arch, Tls>) {
Arc::make_mut(&mut self.modules).push(module);
}
pub fn replace<I, R>(&mut self, modules: I)
where
I: IntoIterator<Item = R>,
R: Into<ModuleHandle<Arch, Tls>>,
{
if let Some(current) = Arc::get_mut(&mut self.modules) {
current.clear();
current.extend(modules.into_iter().map(Into::into));
} else {
self.modules = Arc::new(modules.into_iter().map(Into::into).collect());
}
}
pub fn extend<I, R>(&mut self, modules: I)
where
I: IntoIterator<Item = R>,
R: Into<ModuleHandle<Arch, Tls>>,
{
Arc::make_mut(&mut self.modules).extend(modules.into_iter().map(Into::into));
}
pub fn retain(&mut self, keep: impl FnMut(&ModuleHandle<Arch, Tls>) -> bool) {
Arc::make_mut(&mut self.modules).retain(keep);
}
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch>> Clone for LocalScope<Arch, Tls> {
#[inline]
fn clone(&self) -> Self {
Self {
groups: Arc::clone(&self.groups),
lazy: self.lazy.clone(),
}
}
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch>> Clone for GlobalScope<Arch, Tls> {
#[inline]
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl<Arch, Tls> fmt::Debug for LocalScope<Arch, Tls>
where
Arch: RelocationArch,
Tls: TlsResolver<Arch>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LocalScope")
.field("groups", &self.groups)
.field("lazy", &self.lazy)
.finish()
}
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch>> LocalScope<Arch, Tls> {
#[inline]
pub fn empty(domain: DomainId) -> Self {
Self::new([], ModuleScope::new(domain))
}
pub fn new<I>(groups: I, lazy: ModuleScope<Arch, Tls>) -> Self
where
I: IntoIterator<Item = ModuleScope<Arch, Tls>>,
{
let groups = groups.into_iter().collect::<Vec<_>>();
debug_assert!(
groups
.iter()
.all(|group| group.domain_id() == lazy.domain_id())
);
Self {
groups: Arc::from(groups),
lazy,
}
}
#[inline]
pub const fn domain_id(&self) -> DomainId {
self.lazy.domain_id()
}
#[inline]
pub fn check_domain(&self, expected: DomainId) -> Result<()> {
for group in self.groups.iter() {
group.check_domain(expected)?;
}
self.lazy.check_domain(expected)
}
#[inline]
pub(crate) fn downgrade(
&self,
global: Option<&GlobalScope<Arch, Tls>>,
) -> WeakLocalScope<Arch, Tls> {
let domain = self.domain_id();
debug_assert!(global.is_none_or(|global| global.domain_id() == domain));
WeakLocalScope {
local: Arc::downgrade(&self.lazy.modules),
global: global.map(GlobalScope::downgrade),
domain,
}
}
#[inline]
pub fn groups(&self) -> &[ModuleScope<Arch, Tls>] {
&self.groups
}
#[inline]
pub const fn lazy_scope(&self) -> &ModuleScope<Arch, Tls> {
&self.lazy
}
pub fn push(&mut self, group: ModuleScope<Arch, Tls>) {
debug_assert_eq!(group.domain_id(), self.domain_id());
let mut groups = Vec::with_capacity(self.groups.len() + 1);
groups.extend(self.groups.iter().cloned());
groups.push(group);
self.groups = Arc::from(groups);
}
pub fn extend<I>(&mut self, groups: I)
where
I: IntoIterator<Item = ModuleScope<Arch, Tls>>,
{
let mut groups = groups.into_iter().peekable();
if groups.peek().is_none() {
return;
}
let (lower, _) = groups.size_hint();
let mut current = Vec::with_capacity(self.groups.len() + lower);
current.extend(self.groups.iter().cloned());
for group in groups {
debug_assert_eq!(group.domain_id(), self.domain_id());
current.push(group);
}
self.groups = Arc::from(current);
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &ModuleHandle<Arch, Tls>> {
self.groups.iter().flat_map(|group| group.iter())
}
#[inline]
pub fn len(&self) -> usize {
self.groups.iter().map(|group| group.len()).sum()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.groups.iter().all(|group| group.is_empty())
}
#[inline]
pub fn set_lazy_scope(&mut self, scope: ModuleScope<Arch, Tls>) {
debug_assert_eq!(scope.domain_id(), self.domain_id());
self.lazy = scope;
}
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch>> WeakLocalScope<Arch, Tls> {
#[inline]
pub(crate) fn upgrade_local(&self) -> Option<LocalScope<Arch, Tls>> {
let scope = ModuleScope {
modules: self.local.upgrade()?,
domain: self.domain,
};
Some(LocalScope::new([scope.clone()], scope))
}
#[inline]
pub(crate) fn upgrade_global(&self) -> Option<GlobalScope<Arch, Tls>> {
self.global
.as_ref()
.and_then(Weak::upgrade)
.map(|inner| GlobalScope { inner })
}
}
impl<Arch: RelocationArch, Tls: TlsResolver<Arch>> GlobalScope<Arch, Tls> {
#[inline]
pub(crate) fn new(domain: DomainId) -> Self {
Self {
inner: Arc::new(GlobalScopeInner {
modules: RwLock::new(ModuleScope::new(domain)),
domain,
}),
}
}
#[inline]
pub fn domain_id(&self) -> DomainId {
self.inner.domain
}
#[inline]
fn downgrade(&self) -> Weak<GlobalScopeInner<Arch, Tls>> {
Arc::downgrade(&self.inner)
}
#[inline]
pub fn modules(&self) -> ModuleScope<Arch, Tls> {
self.read().clone()
}
#[inline]
pub(crate) fn read(&self) -> RwLockReadGuard<'_, ModuleScope<Arch, Tls>> {
self.inner.modules.read()
}
#[inline]
pub(crate) fn write(&self) -> RwLockWriteGuard<'_, ModuleScope<Arch, Tls>> {
self.inner.modules.write()
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use crate::image::SyntheticModule;
#[test]
fn shared_module_preserves_identity() {
let module = Arc::new(SyntheticModule::<NativeArch>::empty("shared"));
let first: ModuleHandle = ModuleHandle::new(module.clone());
let second: ModuleHandle = ModuleHandle::new(module);
assert!(first.as_ref().ptr_eq(second.as_ref()));
}
#[test]
fn module_scope_mutation_preserves_snapshots() {
let first: ModuleHandle = ModuleHandle::new(SyntheticModule::<NativeArch>::empty("first"));
let second: ModuleHandle =
ModuleHandle::new(SyntheticModule::<NativeArch>::empty("second"));
let mut scope: ModuleScope = ModuleScope::new(DomainId::PROCESS);
scope.push(first);
let snapshot = scope.clone();
scope.push(second);
assert_eq!(snapshot.len(), 1);
assert_eq!(scope.len(), 2);
scope.retain(|module| module.name() == "second");
assert_eq!(snapshot.iter().next().unwrap().name(), "first");
assert_eq!(scope.iter().next().unwrap().name(), "second");
}
#[test]
fn local_scope_retains_modules_and_tracks_live_global() {
let first: ModuleHandle = ModuleHandle::new(SyntheticModule::<NativeArch>::empty("first"));
let second: ModuleHandle =
ModuleHandle::new(SyntheticModule::<NativeArch>::empty("second"));
let local: ModuleHandle = ModuleHandle::new(SyntheticModule::<NativeArch>::empty("local"));
let global = GlobalScope::new(DomainId::PROCESS);
global.write().push(first);
let prepared = global.modules();
let mut local_scope = ModuleScope::new(DomainId::PROCESS);
local_scope.push(local);
let scope = LocalScope::new([local_scope.clone()], local_scope);
let weak = scope.downgrade(Some(&global));
global.write().replace([second]);
assert_eq!(
prepared
.iter()
.map(|module| module.name())
.collect::<Vec<_>>(),
["first"]
);
assert_eq!(
weak.upgrade_local()
.unwrap()
.iter()
.map(|module| module.name())
.collect::<Vec<_>>(),
["local"]
);
let deferred = weak.upgrade_local().unwrap();
let live = weak.upgrade_global().unwrap();
assert_eq!(
live.modules()
.iter()
.map(|module| module.name())
.collect::<Vec<_>>(),
["second"]
);
assert_eq!(
deferred
.iter()
.map(|module| module.name())
.collect::<Vec<_>>(),
["local"]
);
drop(live);
drop(global);
let deferred = weak.upgrade_local().unwrap();
assert!(weak.upgrade_global().is_none());
assert_eq!(
deferred
.iter()
.map(|module| module.name())
.collect::<Vec<_>>(),
["local"]
);
assert_eq!(
scope.iter().map(|module| module.name()).collect::<Vec<_>>(),
["local"]
);
}
#[test]
fn deferred_scope_uses_retained_dependencies() {
let root: ModuleHandle = ModuleHandle::new(SyntheticModule::<NativeArch>::empty("root"));
let dependency: ModuleHandle =
ModuleHandle::new(SyntheticModule::<NativeArch>::empty("dependency"));
let unrelated: ModuleHandle =
ModuleHandle::new(SyntheticModule::<NativeArch>::empty("unrelated"));
let mut group = ModuleScope::new(DomainId::PROCESS);
group.extend([root, dependency.clone()]);
let mut retained = ModuleScope::new(DomainId::PROCESS);
retained.push(dependency);
let owner = retained.clone();
let mut scope = LocalScope::new([group], retained);
let mut extra = ModuleScope::new(DomainId::PROCESS);
extra.push(unrelated);
scope.push(extra);
let weak = scope.downgrade(None);
assert_eq!(
scope.iter().map(|module| module.name()).collect::<Vec<_>>(),
["root", "dependency", "unrelated"]
);
assert_eq!(
scope
.lazy_scope()
.iter()
.map(|module| module.name())
.collect::<Vec<_>>(),
["dependency"]
);
assert_eq!(
weak.upgrade_local()
.unwrap()
.iter()
.map(|module| module.name())
.collect::<Vec<_>>(),
["dependency"]
);
drop(scope);
drop(owner);
assert!(weak.upgrade_local().is_none());
}
#[test]
fn initializer_panic_marks_module_failed() {
let state = ModuleState::new(ModuleSourceId::fresh(), DomainId::PROCESS);
let panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = state.initialize(|| -> crate::Result<()> { panic!("initializer panic") });
}));
assert!(panic.is_err());
assert!(!state.is_initialized());
assert!(state.initialize(|| Ok(())).is_err());
}
#[test]
fn module_state_ignores_self_binding() {
let module = SyntheticModule::<NativeArch>::empty("module");
let state = <SyntheticModule<NativeArch> as Module<NativeArch>>::state(&module);
let binding = state.instance_id();
state.install_effects([binding], []);
assert!(state.with_effects(|bindings, _| bindings.is_empty()));
}
#[test]
fn binding_distinguishes_reloaded_source() {
let source = ModuleSourceId::fresh();
let old = ModuleState::new(source, DomainId::PROCESS);
let binding = old.instance_id();
let replacement = ModuleState::new(source, DomainId::PROCESS);
assert_eq!(binding, old.instance_id());
assert_ne!(binding, replacement.instance_id());
}
}