use mun_abi::Guid;
use crate::{
diff::{compute_struct_diff, FieldDiff, StructDiff},
gc::GcPtr,
r#type::Type,
ArrayType, TypeKind,
};
use std::collections::{HashMap, HashSet};
pub struct Mapping {
pub deletions: HashSet<Type>,
pub struct_mappings: HashMap<Type, StructMapping>,
pub identical: Vec<(Type, Type)>,
}
pub struct StructMapping {
pub field_mapping: Vec<FieldMapping>,
pub new_ty: Type,
}
#[derive(Debug)]
pub struct FieldMapping {
pub new_ty: Type,
pub new_offset: usize,
pub action: Action,
}
#[derive(Debug, Eq, PartialEq)]
pub enum Action {
ArrayAlloc,
ArrayFromValue {
element_action: Box<Action>,
old_offset: usize,
},
ArrayMap {
element_action: Box<Action>,
old_offset: usize,
},
Cast { old_ty: Type, old_offset: usize },
Copy {
old_offset: usize,
size: usize,
},
ElementFromArray {
element_action: Box<Action>,
old_offset: usize,
},
StructAlloc,
StructMapFromGc { old_ty: Type, old_offset: usize },
StructMapFromValue { old_ty: Type, old_offset: usize },
StructMapInPlace { old_ty: Type, old_offset: usize },
ZeroInitialize,
}
impl Mapping {
#[allow(clippy::mutable_key_type)]
pub fn new(old: &[Type], new: &[Type]) -> Self {
let diff = compute_struct_diff(old, new);
let mut conversions = HashMap::new();
let mut deletions = HashSet::new();
let mut insertions = HashSet::new();
let mut identical = Vec::new();
for diff in diff.iter() {
match diff {
StructDiff::Delete { ty, .. } => {
deletions.insert(ty.clone());
}
StructDiff::Edit {
diff,
old_ty,
new_ty,
..
} => {
conversions.insert(old_ty.clone(), unsafe {
field_mapping(old_ty, new_ty, diff)
});
}
StructDiff::Insert { ty, .. } => {
insertions.insert(ty.clone());
}
StructDiff::Move { old_ty, new_ty, .. } => {
identical.push((old_ty.clone(), new_ty.clone()))
}
}
}
let mut new_candidates: HashSet<_> = new
.iter()
.filter(|ty| ty.is_struct())
.filter(|ty| !insertions.contains(*ty))
.cloned()
.collect();
let mut old_candidates: HashSet<_> = old
.iter()
.filter(|ty| ty.is_struct())
.filter(|ty| !deletions.contains(*ty))
.filter(|ty| {
if let Some(conversion) = conversions.get(*ty) {
new_candidates.remove(&conversion.new_ty);
false
} else {
true
}
})
.cloned()
.collect();
for (old_ty, new_ty) in identical.iter() {
old_candidates.remove(old_ty);
new_candidates.remove(new_ty);
}
for old_ty in old_candidates {
let new_ty = new_candidates.take(&old_ty).unwrap();
identical.push((old_ty, new_ty));
}
debug_assert!(new_candidates.is_empty());
Self {
deletions,
struct_mappings: conversions,
identical,
}
}
}
pub unsafe fn field_mapping(old_ty: &Type, new_ty: &Type, diff: &[FieldDiff]) -> StructMapping {
let old_fields = old_ty
.as_struct()
.map(|s| s.fields().iter().collect())
.unwrap_or_else(Vec::new);
let deletions: HashSet<usize> = diff
.iter()
.filter_map(|diff| match diff {
FieldDiff::Delete { index } => Some(*index),
FieldDiff::Move { old_index, .. } => Some(*old_index),
FieldDiff::Edit { old_index, .. } => *old_index,
FieldDiff::Insert { .. } => None,
})
.collect();
let mut mapping: Vec<Action> = old_fields
.iter()
.enumerate()
.filter_map(|(idx, old_field)| {
if deletions.contains(&idx) {
None
} else {
Some(Action::Copy {
old_offset: old_field.offset(),
size: old_field.ty().reference_layout().size(),
})
}
})
.collect();
let mut additions: Vec<(usize, Action)> = diff
.iter()
.filter_map(|diff| match diff {
FieldDiff::Edit {
old_type,
new_type,
old_index,
new_index,
..
} => old_index.map(|old_index| {
let old_offset = old_fields
.get(old_index)
.map(|field| field.offset())
.expect("The old field must exist.");
(*new_index, resolve_edit(old_type, new_type, old_offset))
}),
FieldDiff::Insert { index, new_type } => Some((
*index,
if new_type.is_struct() && !new_type.is_value_type() {
Action::StructAlloc
} else if new_type.is_array() {
Action::ArrayAlloc
} else {
Action::ZeroInitialize
},
)),
FieldDiff::Move {
ty,
old_index,
new_index,
} => {
let old_offset = old_fields
.get(*old_index)
.map(|field| field.offset())
.expect("Old field must exist.");
Some((
*new_index,
Action::Copy {
old_offset,
size: ty.reference_layout().size(),
},
))
}
FieldDiff::Delete { .. } => None,
})
.collect();
additions.sort_by(|a, b| a.0.cmp(&b.0));
for (new_index, map) in additions {
mapping.insert(new_index, map);
}
for diff in diff.iter() {
if let FieldDiff::Edit {
old_type,
new_type,
old_index: None,
new_index,
..
} = diff
{
let old_offset = old_fields
.get(*new_index)
.map(|field| field.offset())
.expect("The old field must exist.");
let action = mapping.get_mut(*new_index).unwrap();
*action = resolve_edit(old_type, new_type, old_offset);
}
}
let new_fields = new_ty
.as_struct()
.map(|s| Vec::from_iter(s.fields().iter()))
.unwrap_or_else(Vec::new);
StructMapping {
field_mapping: mapping
.into_iter()
.enumerate()
.map(|(new_index, action)| {
let new_field = new_fields
.get(new_index)
.unwrap_or_else(|| panic!("New field at index: '{}' must exist.", new_index));
FieldMapping {
new_ty: new_field.ty(),
new_offset: new_field.offset(),
action,
}
})
.collect(),
new_ty: new_ty.clone(),
}
}
pub fn resolve_edit(old_ty: &Type, new_ty: &Type, old_offset: usize) -> Action {
match &old_ty.kind() {
TypeKind::Primitive(old_guid) => {
resolve_primitive_edit(old_ty, new_ty, old_guid, old_offset)
}
TypeKind::Struct(_) => resolve_struct_edit(old_ty, new_ty, old_offset),
TypeKind::Pointer(_) => resolve_pointer_edit(old_ty, new_ty),
TypeKind::Array(old_array) => resolve_array_edit(old_array, new_ty, old_offset),
}
}
fn resolve_primitive_edit(
old_ty: &Type,
new_ty: &Type,
old_guid: &Guid,
old_offset: usize,
) -> Action {
match &new_ty.kind() {
TypeKind::Primitive(new_guid) => {
resolve_primitive_to_primitive_edit(old_ty, old_guid, old_offset, new_guid)
}
TypeKind::Struct(s) => {
if s.is_value_struct() {
Action::ZeroInitialize
} else {
Action::StructAlloc
}
}
TypeKind::Pointer(_) => unreachable!(),
TypeKind::Array(new_array) => {
resolve_primitive_to_array_edit(old_ty, new_array, old_offset)
}
}
}
fn resolve_primitive_to_primitive_edit(
old_ty: &Type,
old_guid: &Guid,
old_offset: usize,
new_guid: &Guid,
) -> Action {
if *old_guid == *new_guid {
Action::Copy {
old_offset,
size: old_ty.value_layout().size(),
}
} else {
Action::Cast {
old_ty: old_ty.clone(),
old_offset,
}
}
}
fn resolve_primitive_to_array_edit(
old_ty: &Type,
new_array: &ArrayType,
old_offset: usize,
) -> Action {
Action::ArrayFromValue {
element_action: Box::new(resolve_edit(old_ty, &new_array.element_type(), 0)),
old_offset,
}
}
fn resolve_struct_edit(old_ty: &Type, new_ty: &Type, old_offset: usize) -> Action {
match &new_ty.kind() {
TypeKind::Primitive(_) => Action::ZeroInitialize,
TypeKind::Struct(_) => resolve_struct_to_struct_edit(old_ty, new_ty, old_offset),
TypeKind::Pointer(_) => unreachable!(),
TypeKind::Array(new_array) => resolve_struct_to_array_edit(old_ty, new_array, old_offset),
}
}
pub fn resolve_struct_to_struct_edit(old_ty: &Type, new_ty: &Type, old_offset: usize) -> Action {
if *old_ty == *new_ty {
return Action::Copy {
old_offset,
size: old_ty.reference_layout().size(),
};
}
let is_same_struct = old_ty.name() == new_ty.name();
if old_ty.is_value_type() && new_ty.is_value_type() {
if is_same_struct {
Action::StructMapInPlace {
old_ty: old_ty.clone(),
old_offset,
}
} else {
Action::ZeroInitialize
}
} else if old_ty.is_value_type() {
if is_same_struct {
Action::StructMapFromValue {
old_ty: old_ty.clone(),
old_offset,
}
} else {
Action::StructAlloc
}
} else if new_ty.is_value_type() {
if is_same_struct {
Action::StructMapFromGc {
old_ty: old_ty.clone(),
old_offset,
}
} else {
Action::ZeroInitialize
}
} else {
if is_same_struct {
Action::Copy {
old_offset,
size: std::mem::size_of::<GcPtr>(),
}
} else {
Action::StructAlloc
}
}
}
fn resolve_struct_to_array_edit(old_ty: &Type, new_array: &ArrayType, old_offset: usize) -> Action {
Action::ArrayFromValue {
element_action: Box::new(resolve_edit(old_ty, &new_array.element_type(), 0)),
old_offset,
}
}
fn resolve_pointer_edit(_old_ty: &Type, _new_ty: &Type) -> Action {
unreachable!()
}
fn resolve_array_edit(old_array: &ArrayType, new_ty: &Type, old_offset: usize) -> Action {
match &new_ty.kind() {
TypeKind::Primitive(_) => resolve_array_to_primitive_edit(old_array, new_ty, old_offset),
TypeKind::Struct(_) => resolve_array_to_struct_edit(old_array, new_ty, old_offset),
TypeKind::Pointer(_) => unreachable!(),
TypeKind::Array(new_array) => resolve_array_to_array_edit(old_array, new_array, old_offset),
}
}
fn resolve_array_to_primitive_edit(
old_array: &ArrayType,
new_ty: &Type,
old_offset: usize,
) -> Action {
Action::ElementFromArray {
old_offset,
element_action: Box::new(resolve_edit(&old_array.element_type(), new_ty, 0)),
}
}
fn resolve_array_to_struct_edit(old_array: &ArrayType, new_ty: &Type, old_offset: usize) -> Action {
Action::ElementFromArray {
old_offset,
element_action: Box::new(resolve_edit(&old_array.element_type(), new_ty, 0)),
}
}
fn resolve_array_to_array_edit(
old_array: &ArrayType,
new_array: &ArrayType,
old_offset: usize,
) -> Action {
let old_element_type = old_array.element_type();
let new_element_type = new_array.element_type();
if old_element_type == new_element_type {
Action::Copy {
old_offset,
size: std::mem::size_of::<GcPtr>(),
}
} else {
Action::ArrayMap {
element_action: Box::new(resolve_edit(&old_element_type, &new_element_type, 0)),
old_offset,
}
}
}
pub trait MemoryMapper {
fn map_memory(&self, mapping: Mapping) -> Vec<GcPtr>;
}