use alloc::{format, sync::Arc};
use core::sync::atomic::AtomicUsize;
use crate::{GlobalVariable, Handle, Module, Type, TypeInner};
#[derive(Clone, Debug, thiserror::Error)]
pub enum Error {
#[error("encountered an unsupported expression")]
Unsupported,
#[error("unexpected end of struct field access indices")]
UnexpectedEndOfIndices,
#[error("encountered unsupported global initializer in an atomic variable")]
GlobalInitUnsupported,
#[error("expected to find a global variable")]
GlobalVariableMissing,
#[error("atomic compare exchange requires a scalar base type")]
CompareExchangeNonScalarBaseType,
}
#[derive(Clone, Default)]
struct Padding(Arc<AtomicUsize>);
impl core::fmt::Display for Padding {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for _ in 0..self.0.load(core::sync::atomic::Ordering::Relaxed) {
f.write_str(" ")?;
}
Ok(())
}
}
impl Drop for Padding {
fn drop(&mut self) {
let _ = self.0.fetch_sub(1, core::sync::atomic::Ordering::Relaxed);
}
}
impl Padding {
fn trace(&self, msg: impl core::fmt::Display, t: impl core::fmt::Debug) {
format!("{msg} {t:#?}")
.split('\n')
.for_each(|ln| log::trace!("{self}{ln}"));
}
fn debug(&self, msg: impl core::fmt::Display, t: impl core::fmt::Debug) {
format!("{msg} {t:#?}")
.split('\n')
.for_each(|ln| log::debug!("{self}{ln}"));
}
fn inc_padding(&self) -> Padding {
let _ = self.0.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
self.clone()
}
}
#[derive(Debug, Default)]
pub struct Upgrades {
globals: crate::arena::HandleSet<GlobalVariable>,
fields: crate::FastHashMap<Handle<Type>, bit_set::BitSet>,
}
impl Upgrades {
pub fn insert_global(&mut self, global: Handle<GlobalVariable>) {
self.globals.insert(global);
}
pub fn insert_field(&mut self, struct_type: Handle<Type>, field: usize) {
self.fields.entry(struct_type).or_default().insert(field);
}
pub fn is_empty(&self) -> bool {
self.globals.is_empty()
}
}
struct UpgradeState<'a> {
padding: Padding,
module: &'a mut Module,
upgraded_types: crate::FastHashMap<Handle<Type>, Handle<Type>>,
}
impl UpgradeState<'_> {
fn inc_padding(&self) -> Padding {
self.padding.inc_padding()
}
fn upgrade_type(
&mut self,
ty: Handle<Type>,
upgrades: &Upgrades,
) -> Result<Handle<Type>, Error> {
let padding = self.inc_padding();
padding.trace("visiting type: ", ty);
if let Some(&new) = self.upgraded_types.get(&ty) {
return Ok(new);
}
let inner = match self.module.types[ty].inner {
TypeInner::Scalar(scalar) => {
log::trace!("{padding}hit the scalar leaf, replacing with an atomic");
TypeInner::Atomic(scalar)
}
TypeInner::Pointer { base, space } => TypeInner::Pointer {
base: self.upgrade_type(base, upgrades)?,
space,
},
TypeInner::Array { base, size, stride } => TypeInner::Array {
base: self.upgrade_type(base, upgrades)?,
size,
stride,
},
TypeInner::Struct { ref members, span } => {
let Some(fields) = upgrades.fields.get(&ty) else {
unreachable!("global or field incorrectly flagged as atomically accessed");
};
let mut new_members = members.clone();
for field in fields {
new_members[field].ty = self.upgrade_type(new_members[field].ty, upgrades)?;
}
TypeInner::Struct {
members: new_members,
span,
}
}
TypeInner::BindingArray { base, size } => TypeInner::BindingArray {
base: self.upgrade_type(base, upgrades)?,
size,
},
_ => return Ok(ty),
};
let r#type = &self.module.types[ty];
let span = self.module.types.get_span(ty);
let new_type = Type {
name: r#type.name.clone(),
inner,
};
padding.debug("ty: ", ty);
padding.debug("from: ", r#type);
padding.debug("to: ", &new_type);
let new_handle = self.module.types.insert(new_type, span);
self.upgraded_types.insert(ty, new_handle);
Ok(new_handle)
}
fn upgrade_all(&mut self, upgrades: &Upgrades) -> Result<(), Error> {
for handle in upgrades.globals.iter() {
let padding = self.inc_padding();
let global = &self.module.global_variables[handle];
padding.trace("visiting global variable: ", handle);
padding.trace("var: ", global);
if global.init.is_some() {
return Err(Error::GlobalInitUnsupported);
}
let var_ty = global.ty;
let new_ty = self.upgrade_type(var_ty, upgrades)?;
if new_ty != var_ty {
padding.debug("upgrading global variable: ", handle);
padding.debug("from ty: ", var_ty);
padding.debug("to ty: ", new_ty);
self.module.global_variables[handle].ty = new_ty;
}
}
Ok(())
}
}
impl Module {
pub(crate) fn upgrade_atomics(&mut self, upgrades: &Upgrades) -> Result<(), Error> {
let mut state = UpgradeState {
padding: Default::default(),
module: self,
upgraded_types: crate::FastHashMap::with_capacity_and_hasher(
upgrades.fields.len(),
Default::default(),
),
};
state.upgrade_all(upgrades)?;
Ok(())
}
}