spimdisasm 2.0.0-alpha.1

MIPS disassembler
Documentation
/* SPDX-FileCopyrightText: © 2024-2025 Decompollaborate */
/* SPDX-License-Identifier: MIT */

use alloc::{collections::btree_set::BTreeSet, sync::Arc, vec::Vec};
use core::hash;

#[cfg(feature = "pyo3")]
use pyo3::prelude::*;

use crate::{
    addresses::{AddressRange, Vram},
    collections::{unordered_map::UnorderedMap, unordered_set::UnorderedSet},
    config::Compiler,
    context::Context,
    metadata::ParentSectionMetadata,
    parent_segment_info::ParentSegmentInfo,
    section_type::SectionType,
    sections::{processed::NobitsSectionProcessed, EmptySectionError, SectionPreprocessed},
    symbols::{
        before_proc::{nobits_sym::NobitsSymProperties, NobitsSym},
        Symbol, SymbolPreprocessed,
    },
};

use crate::sections::{Section, SectionCreationError, SectionPostProcessError};

#[derive(Debug, Clone)]
#[must_use]
pub struct NobitsSection {
    name: Arc<str>,

    vram_range: AddressRange<Vram>,

    parent_segment_info: ParentSegmentInfo,

    // in_section_offset: u32,

    //
    nobits_symbols: Vec<NobitsSym>,

    symbol_vrams: UnorderedSet<Vram>,
}

impl NobitsSection {
    pub(crate) fn new(
        context: &mut Context,
        settings: &NobitsSectionSettings,
        name: Arc<str>,
        vram_range: AddressRange<Vram>,
        parent_segment_info: ParentSegmentInfo,
    ) -> Result<Self, SectionCreationError> {
        if vram_range.size().inner() == 0 {
            return Err(EmptySectionError::new(name, vram_range.start()).into());
        }

        let mut nobits_symbols = Vec::new();
        let mut symbol_vrams = UnorderedSet::new();

        let owned_segment = context.find_owned_segment(&parent_segment_info)?;

        let mut symbols_info = BTreeSet::new();
        // Ensure there's a symbol at the beginning of the section.
        symbols_info.insert(vram_range.start());

        let mut auto_pads: UnorderedMap<Vram, Vram> = UnorderedMap::new();

        /*
        # If something that could be a pointer found in data happens to be in
        # the middle of this bss file's addresses space then consider it as a
        # new bss variable
        for ptr in self.getAndPopPointerInDataReferencesRange(self.bssVramStart, self.bssVramEnd):
            # Check if the symbol already exists, in case the user has provided size
            contextSym = self.getSymbol(ptr, tryPlusOffset=True)
            if contextSym is None:
                self.addSymbol(ptr, sectionType=self.sectionType, isAutogenerated=True)
        */

        for reference in owned_segment.find_references_range(vram_range) {
            if !owned_segment.is_vram_ignored(reference.vram()) {
                symbols_info.insert(reference.vram());
            }

            if let Some(size) = reference.user_declared_size() {
                let next_vram = reference.vram() + size;
                if next_vram != vram_range.end() && !owned_segment.is_vram_ignored(next_vram) {
                    // Avoid generating a symbol at the end of the section
                    symbols_info.insert(next_vram);
                    auto_pads.insert(next_vram, reference.vram());
                }
            }
        }

        let symbols_info_vec: Vec<Vram> = symbols_info.into_iter().collect();

        for (i, new_sym_vram) in symbols_info_vec.iter().enumerate() {
            let new_sym_vram_end = if i + 1 < symbols_info_vec.len() {
                symbols_info_vec[i + 1]
            } else {
                vram_range.end()
            };
            debug_assert!(
                *new_sym_vram < new_sym_vram_end,
                "{vram_range:?} {new_sym_vram} {new_sym_vram_end}"
            );

            symbol_vrams.insert(*new_sym_vram);

            let properties = NobitsSymProperties {
                parent_metadata: ParentSectionMetadata::new(
                    name.clone(),
                    vram_range.start(),
                    parent_segment_info.clone(),
                ),
                compiler: settings.compiler,
                auto_pad_by: auto_pads.get(new_sym_vram).copied(),
            };
            let sym = NobitsSym::new(
                context,
                AddressRange::new(*new_sym_vram, new_sym_vram_end),
                parent_segment_info.clone(),
                properties,
            )?;

            nobits_symbols.push(sym);
        }

        Ok(Self {
            name,
            vram_range,
            parent_segment_info,
            nobits_symbols,
            symbol_vrams,
        })
    }
}

impl NobitsSection {
    pub fn nobits_symbols(&self) -> &[NobitsSym] {
        &self.nobits_symbols
    }
}

impl NobitsSection {
    pub fn post_process(
        self,
        context: &mut Context,
    ) -> Result<NobitsSectionProcessed, SectionPostProcessError> {
        NobitsSectionProcessed::new(
            context,
            self.name,
            self.vram_range,
            self.parent_segment_info,
            self.nobits_symbols,
            self.symbol_vrams,
        )
    }
}

impl Section for NobitsSection {
    fn name(&self) -> Arc<str> {
        self.name.clone()
    }

    fn vram_range(&self) -> &AddressRange<Vram> {
        &self.vram_range
    }

    fn parent_segment_info(&self) -> &ParentSegmentInfo {
        &self.parent_segment_info
    }

    fn section_type(&self) -> SectionType {
        SectionType::Bss
    }

    fn symbol_list(&self) -> &[impl Symbol] {
        &self.nobits_symbols
    }

    fn symbols_vrams(&self) -> &UnorderedSet<Vram> {
        &self.symbol_vrams
    }
}
impl SectionPreprocessed for NobitsSection {
    fn symbol_list(&self) -> &[impl SymbolPreprocessed] {
        &self.nobits_symbols
    }
}

impl hash::Hash for NobitsSection {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.parent_segment_info.hash(state);
        self.vram_range.hash(state);
    }
}
impl PartialEq for NobitsSection {
    fn eq(&self, other: &Self) -> bool {
        self.parent_segment_info == other.parent_segment_info && self.vram_range == other.vram_range
    }
}
impl PartialOrd for NobitsSection {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        // Compare segment info first, so symbols get sorted by segment
        match self
            .parent_segment_info
            .partial_cmp(&other.parent_segment_info)
        {
            Some(core::cmp::Ordering::Equal) => {}
            ord => return ord,
        }
        self.vram_range.partial_cmp(&other.vram_range)
    }
}

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "pyo3", pyclass(module = "spimdisasm"))]
pub struct NobitsSectionSettings {
    compiler: Option<Compiler>,
}

impl NobitsSectionSettings {
    pub fn new(compiler: Option<Compiler>) -> Self {
        Self { compiler }
    }
}

#[cfg(feature = "pyo3")]
pub(crate) mod python_bindings {
    use super::*;

    #[pymethods]
    impl NobitsSectionSettings {
        #[new]
        #[pyo3(signature = (compiler))]
        pub fn py_new(compiler: Option<Compiler>) -> Self {
            Self::new(compiler)
        }
    }
}