use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use crate::builder::Builder;
use crate::entity::{AggId, FuncId, GlobalId, SigId};
use crate::error::{ModuleError, ValidationError};
use crate::function::Function;
use crate::inst::Attrs;
use crate::types::{Aggregate, Layout, MAX_AGGREGATE_SIZE, MemType, PointerWidth, Signature};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum Linkage {
Local,
Export,
Import,
}
impl Linkage {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Linkage::Local => "local",
Linkage::Export => "export",
Linkage::Import => "import",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Linkage> {
match name {
"local" => Some(Linkage::Local),
"export" => Some(Linkage::Export),
"import" => Some(Linkage::Import),
_ => None,
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct FuncDecl {
pub(crate) name: String,
pub(crate) sig: SigId,
pub(crate) linkage: Linkage,
pub(crate) attrs: Attrs,
}
impl FuncDecl {
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn sig(&self) -> SigId {
self.sig
}
#[must_use]
pub fn linkage(&self) -> Linkage {
self.linkage
}
#[must_use]
pub fn attrs(&self) -> Attrs {
self.attrs
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum SymbolRef {
Func(FuncId),
Global(GlobalId),
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Reloc {
pub offset: u64,
pub target: SymbolRef,
pub addend: i64,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum Init {
Zeroed,
Bytes(Vec<u8>),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Global {
pub linkage: Linkage,
pub size: u64,
pub align: u32,
pub mutable: bool,
pub init: Init,
pub relocs: Vec<Reloc>,
}
impl Global {
pub const MAX_ALIGN: u32 = 1 << 16;
#[must_use]
pub fn zeroed(size: u64, align: u32) -> Global {
Global {
linkage: Linkage::Export,
size,
align,
mutable: false,
init: Init::Zeroed,
relocs: Vec::new(),
}
}
#[must_use]
pub fn bytes(bytes: &[u8], align: u32) -> Global {
Global {
linkage: Linkage::Export,
size: bytes.len() as u64,
align,
mutable: false,
init: Init::Bytes(bytes.to_vec()),
relocs: Vec::new(),
}
}
#[must_use]
pub fn import(size: u64, align: u32) -> Global {
Global {
linkage: Linkage::Import,
..Global::zeroed(size, align)
}
}
#[must_use]
pub fn mutable(mut self) -> Global {
self.mutable = true;
self
}
#[must_use]
pub fn with_linkage(mut self, linkage: Linkage) -> Global {
self.linkage = linkage;
self
}
#[must_use]
pub fn with_reloc(mut self, reloc: Reloc) -> Global {
self.relocs.push(reloc);
self
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct AggData {
pub(crate) agg: Aggregate,
pub(crate) layout: Layout,
pub(crate) offsets: Vec<u64>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct GlobalDecl {
pub(crate) name: String,
pub(crate) global: Global,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Decls {
pub(crate) ptr: PointerWidth,
pub(crate) sigs: Vec<Signature>,
pub(crate) sig_index: BTreeMap<Signature, SigId>,
pub(crate) aggs: Vec<AggData>,
pub(crate) funcs: Vec<FuncDecl>,
pub(crate) globals: Vec<GlobalDecl>,
}
impl Decls {
pub(crate) fn layout(&self, ty: MemType) -> Option<Layout> {
match ty {
MemType::Scalar(t) => {
let size = t.size(self.ptr);
Some(Layout {
size: u64::from(size),
align: size.max(1),
})
}
MemType::Agg(a) => self.aggs.get(a.index()).map(|d| d.layout),
}
}
pub(crate) fn func_sig(&self, func: FuncId) -> Option<&Signature> {
self.funcs
.get(func.index())
.and_then(|d| self.sigs.get(d.sig.index()))
}
pub(crate) fn compute_layout(
&self,
agg: &Aggregate,
limit: usize,
) -> Result<(Layout, Vec<u64>), ModuleError> {
let component = |m: MemType| -> Result<Layout, ModuleError> {
if let MemType::Agg(a) = m {
if a.index() >= limit {
return Err(ModuleError::UnknownAggregate { agg: a });
}
}
self.layout(m).ok_or(ModuleError::AggregateTooLarge)
};
match agg {
Aggregate::Struct { fields, packed } => {
let mut offset = 0u64;
let mut align = 1u32;
let mut offsets = Vec::with_capacity(fields.len());
for &field in fields {
let l = component(field)?;
if !packed {
offset = round_up(offset, l.align).ok_or(ModuleError::AggregateTooLarge)?;
align = align.max(l.align);
}
offsets.push(offset);
offset = offset
.checked_add(l.size)
.filter(|&s| s <= MAX_AGGREGATE_SIZE)
.ok_or(ModuleError::AggregateTooLarge)?;
}
let size = if *packed {
offset
} else {
round_up(offset, align).ok_or(ModuleError::AggregateTooLarge)?
};
if size > MAX_AGGREGATE_SIZE {
return Err(ModuleError::AggregateTooLarge);
}
Ok((Layout { size, align }, offsets))
}
Aggregate::Array { elem, len } => {
let l = component(*elem)?;
let size = l
.size
.checked_mul(*len)
.filter(|&s| s <= MAX_AGGREGATE_SIZE)
.ok_or(ModuleError::AggregateTooLarge)?;
Ok((
Layout {
size,
align: l.align,
},
Vec::new(),
))
}
}
}
}
pub(crate) fn round_up(value: u64, align: u32) -> Option<u64> {
let a = u64::from(align.max(1));
let rem = value % a;
if rem == 0 {
Some(value)
} else {
value.checked_add(a - rem)
}
}
pub(crate) fn valid_align(align: u64, max: u64) -> bool {
align != 0 && align.is_power_of_two() && align <= max
}
#[derive(Clone, Debug)]
pub struct Module {
pub(crate) name: String,
pub(crate) decls: Decls,
pub(crate) bodies: Vec<Option<Function>>,
pub(crate) names: BTreeMap<String, SymbolRef>,
}
impl Module {
#[must_use]
pub fn new(name: &str) -> Module {
Module::with_pointer_width(name, PointerWidth::P64)
}
#[must_use]
pub fn with_pointer_width(name: &str, ptr: PointerWidth) -> Module {
Module {
name: name.to_string(),
decls: Decls {
ptr,
..Decls::default()
},
bodies: Vec::new(),
names: BTreeMap::new(),
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn pointer_width(&self) -> PointerWidth {
self.decls.ptr
}
pub fn intern_signature(&mut self, sig: &Signature) -> Result<SigId, ModuleError> {
if let Some(&id) = self.decls.sig_index.get(sig) {
return Ok(id);
}
let id = SigId::from_u32(next_index(self.decls.sigs.len())?);
self.decls.sigs.push(sig.clone());
let _ = self.decls.sig_index.insert(sig.clone(), id);
Ok(id)
}
#[must_use]
pub fn signature(&self, sig: SigId) -> Option<&Signature> {
self.decls.sigs.get(sig.index())
}
#[must_use]
pub fn signature_count(&self) -> usize {
self.decls.sigs.len()
}
pub fn declare_aggregate(&mut self, agg: Aggregate) -> Result<AggId, ModuleError> {
let index = next_index(self.decls.aggs.len())?;
let (layout, offsets) = self.decls.compute_layout(&agg, index as usize)?;
self.decls.aggs.push(AggData {
agg,
layout,
offsets,
});
Ok(AggId::from_u32(index))
}
#[must_use]
pub fn aggregate(&self, agg: AggId) -> Option<&Aggregate> {
self.decls.aggs.get(agg.index()).map(|d| &d.agg)
}
#[must_use]
pub fn aggregate_count(&self) -> usize {
self.decls.aggs.len()
}
#[must_use]
pub fn layout(&self, ty: MemType) -> Option<Layout> {
self.decls.layout(ty)
}
#[must_use]
pub fn field_offset(&self, agg: AggId, field: u32) -> Option<u64> {
let data = self.decls.aggs.get(agg.index())?;
match data.agg {
Aggregate::Struct { .. } => data.offsets.get(field as usize).copied(),
Aggregate::Array { .. } => None,
}
}
fn claim_name(&self, name: &str) -> Result<(), ModuleError> {
if name.is_empty() {
return Err(ModuleError::EmptyName);
}
if self.names.contains_key(name) {
return Err(ModuleError::DuplicateName {
name: name.to_string(),
});
}
Ok(())
}
pub fn declare_function(
&mut self,
name: &str,
sig: &Signature,
linkage: Linkage,
) -> Result<FuncId, ModuleError> {
self.claim_name(name)?;
let index = next_index(self.decls.funcs.len())?;
let sig = self.intern_signature(sig)?;
let id = FuncId::from_u32(index);
self.decls.funcs.push(FuncDecl {
name: name.to_string(),
sig,
linkage,
attrs: Attrs::NONE,
});
self.bodies.push(None);
let _ = self.names.insert(name.to_string(), SymbolRef::Func(id));
Ok(id)
}
pub fn set_attrs(&mut self, func: FuncId, attrs: Attrs) -> Result<(), ModuleError> {
let decl = self
.decls
.funcs
.get_mut(func.index())
.ok_or(ModuleError::UnknownFunction { func })?;
decl.attrs = attrs;
Ok(())
}
#[must_use]
pub fn func_decl(&self, func: FuncId) -> Option<&FuncDecl> {
self.decls.funcs.get(func.index())
}
#[must_use]
pub fn func_signature(&self, func: FuncId) -> Option<&Signature> {
self.decls.func_sig(func)
}
#[must_use]
pub fn function_count(&self) -> usize {
self.decls.funcs.len()
}
pub fn functions(&self) -> impl ExactSizeIterator<Item = FuncId> {
(0..self.decls.funcs.len() as u32).map(FuncId::from_u32)
}
#[must_use]
pub fn function(&self, func: FuncId) -> Option<&Function> {
self.bodies.get(func.index()).and_then(Option::as_ref)
}
pub fn declare_global(&mut self, name: &str, global: Global) -> Result<GlobalId, ModuleError> {
self.claim_name(name)?;
check_global(&global, self.decls.ptr)?;
let index = next_index(self.decls.globals.len())?;
let id = GlobalId::from_u32(index);
self.decls.globals.push(GlobalDecl {
name: name.to_string(),
global,
});
let _ = self.names.insert(name.to_string(), SymbolRef::Global(id));
Ok(id)
}
#[must_use]
pub fn global(&self, global: GlobalId) -> Option<&Global> {
self.decls.globals.get(global.index()).map(|g| &g.global)
}
#[must_use]
pub fn global_name(&self, global: GlobalId) -> Option<&str> {
self.decls
.globals
.get(global.index())
.map(|g| g.name.as_str())
}
#[must_use]
pub fn global_count(&self) -> usize {
self.decls.globals.len()
}
pub fn globals(&self) -> impl ExactSizeIterator<Item = GlobalId> {
(0..self.decls.globals.len() as u32).map(GlobalId::from_u32)
}
#[must_use]
pub fn lookup(&self, name: &str) -> Option<SymbolRef> {
self.names.get(name).copied()
}
pub fn build(&mut self, func: FuncId) -> Result<Builder<'_>, ModuleError> {
let decl = self
.decls
.funcs
.get(func.index())
.ok_or(ModuleError::UnknownFunction { func })?;
if decl.linkage == Linkage::Import {
return Err(ModuleError::CannotDefineImport { func });
}
let params = self
.decls
.sigs
.get(decl.sig.index())
.map(|s| s.params.clone())
.unwrap_or_default();
let slot = self
.bodies
.get_mut(func.index())
.ok_or(ModuleError::UnknownFunction { func })?;
if slot.is_some() {
return Err(ModuleError::AlreadyDefined { func });
}
let body = slot.insert(Function::new(¶ms));
Ok(Builder::new(&self.decls, body, func))
}
pub fn edit(&mut self, func: FuncId) -> Result<Builder<'_>, ModuleError> {
let body = self
.bodies
.get_mut(func.index())
.ok_or(ModuleError::UnknownFunction { func })?
.as_mut()
.ok_or(ModuleError::NotDefined { func })?;
Ok(Builder::new(&self.decls, body, func))
}
pub fn validate(&self) -> Result<(), ValidationError> {
crate::validate::validate_module(self)
}
pub fn validate_function(&self, func: FuncId) -> Result<(), ValidationError> {
crate::validate::validate_one(self, func)
}
#[must_use]
pub fn display_function(&self, func: FuncId) -> impl core::fmt::Display + '_ {
crate::print::FunctionDisplay { module: self, func }
}
pub(crate) fn set_body(&mut self, func: FuncId, body: Function) {
if let Some(slot) = self.bodies.get_mut(func.index()) {
*slot = Some(body);
}
}
}
fn next_index(len: usize) -> Result<u32, ModuleError> {
u32::try_from(len)
.ok()
.filter(|&i| i < crate::function::MAX_ENTITIES)
.ok_or(ModuleError::CapacityExceeded)
}
pub(crate) fn check_global(global: &Global, ptr: PointerWidth) -> Result<(), ModuleError> {
if !valid_align(u64::from(global.align), u64::from(Global::MAX_ALIGN)) {
return Err(ModuleError::BadAlignment {
align: u64::from(global.align),
});
}
if global.linkage == Linkage::Import
&& (global.init != Init::Zeroed || !global.relocs.is_empty())
{
return Err(ModuleError::ImportWithData);
}
if let Init::Bytes(bytes) = &global.init {
if bytes.len() as u64 != global.size {
return Err(ModuleError::InitSizeMismatch {
size: global.size,
found: bytes.len() as u64,
});
}
}
for r in &global.relocs {
let fits = r
.offset
.checked_add(u64::from(ptr.bytes()))
.is_some_and(|end| end <= global.size);
if !fits {
return Err(ModuleError::RelocOutOfRange { offset: r.offset });
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Type;
#[test]
fn test_struct_layout_follows_c_rules() {
let mut m = Module::new("m");
let s = m
.declare_aggregate(Aggregate::structure(&[
Type::U8.into(),
Type::U32.into(),
Type::U16.into(),
]))
.unwrap_or(AggId::from_u32(0));
let l = m.layout(s.into()).unwrap_or(Layout { size: 0, align: 0 });
assert_eq!((l.size, l.align), (12, 4));
assert_eq!(m.field_offset(s, 0), Some(0));
assert_eq!(m.field_offset(s, 1), Some(4));
assert_eq!(m.field_offset(s, 2), Some(8));
}
#[test]
fn test_aggregate_cannot_name_a_later_aggregate() {
let mut m = Module::new("m");
let err = m.declare_aggregate(Aggregate::array(AggId::from_u32(0).into(), 2));
assert_eq!(
err,
Err(ModuleError::UnknownAggregate {
agg: AggId::from_u32(0)
})
);
}
#[test]
fn test_huge_aggregate_is_refused() {
let mut m = Module::new("m");
let err = m.declare_aggregate(Aggregate::array(Type::U64.into(), u64::MAX / 4));
assert_eq!(err, Err(ModuleError::AggregateTooLarge));
}
#[test]
fn test_global_checks() {
let mut m = Module::new("m");
assert_eq!(
m.declare_global(
"x",
Global {
init: Init::Bytes(alloc::vec![1]),
..Global::zeroed(2, 1)
}
),
Err(ModuleError::InitSizeMismatch { size: 2, found: 1 })
);
let reloc = Reloc {
offset: 4,
target: SymbolRef::Global(GlobalId::from_u32(0)),
addend: 0,
};
assert_eq!(
m.declare_global("y", Global::zeroed(8, 8).with_reloc(reloc)),
Err(ModuleError::RelocOutOfRange { offset: 4 })
);
assert_eq!(
m.declare_global("", Global::zeroed(8, 8)),
Err(ModuleError::EmptyName)
);
}
}