looklook 0.9.0

Descriptive signal synthesiser.
// Copyright 2026 Gabriel Bjørnager Jensen.
//
// This file is part of LOOKLOOK.
//
// LOOKLOOK is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later ver-
// sion.
//
// LOOKLOOK is distributed in the hope that it will be useful, but WITHOUT ANY WAR-
// RANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License along
// with LOOKLOOK. If not, see <https://www.gnu.org/licenses/>.

//! The [`Mem`] type.

mod tests;

use crate::isa::{Byte, Long, Word};
use crate::vm::{Exception, Segment};

use std::hint::cold_path;
use std::slice;

/// A memory (management unit.)
///
/// # Memory map
///
/// | Segment | Origin     | Size (KiB) | Name    |
/// | ------: | ---------: | ---------: | :------ |
/// |       0 | `0x000000` |       1024 | `IMG0`  |
/// |       1 | `0x100000` |       1024 | `IMG1`  |
/// |       2 | `0x200000` |       1024 | `IMG2`  |
/// |       3 | `0x300000` |       1024 | `IMG3`  |
/// |       4 | `0x400000` |       1024 | `IMG4`  |
/// |       5 | `0x500000` |       1024 | `IMG5`  |
/// |       6 | `0x600000` |       1024 | `IMG6`  |
/// |       7 | `0x700000` |       1024 | `IMG7`  |
/// |      12 | `0xC00000` |        512 | `WRAM`  |
/// |      15 | `0xF00000` |          1 | `IO`    |
#[derive(Clone, Debug)]
pub struct Mem {
	/// The data address in the host's memory.
	data: Box<[Long]>,
}

const _: () = assert!(
	Mem::IMG_SIZE.is_multiple_of(size_of::<Long>()),
	"erroneous image size is not a multiple of long-words",
);

const _: () = assert!(
	Mem::DATA_SIZE.is_multiple_of(size_of::<Long>()),
	"erroneous data size is not a multiple of long-words",
);

impl Mem {
	/// The total size of the image segments.
	pub const IMG_SIZE: usize = {
		let mut size = 0;
		size += Segment::IMG0.size;
		size += Segment::IMG1.size;
		size += Segment::IMG2.size;
		size += Segment::IMG3.size;
		size += Segment::IMG4.size;
		size += Segment::IMG5.size;
		size += Segment::IMG6.size;
		size += Segment::IMG7.size;

		size
	};

	/// The total size of the raw buffer.
	pub const DATA_SIZE: usize = Self::IMG_SIZE + Segment::WRAM.size + Segment::IO.size;

	/// Allocates a memory.
	#[inline]
	#[must_use]
	pub fn new() -> Self {
		const DATA_LEN: usize = Mem::DATA_SIZE >> 2;

		let data = vec![Default::default(); DATA_LEN].into();
		Self { data }
	}

	/// Zeroes the entire memory space.
	#[inline]
	pub fn clear(&mut self) {
		self.data.fill(Default::default());
	}

	/// Maps a virtual address to a physical address.
	///
	/// # Errors
	///
	/// An exception is returned if the address is unimplemented.
	#[inline]
	pub fn map_addr(addr: Long) -> Result<usize, Exception> {
		let segment = Segment::from_v_addr(addr);

		let mut addr = addr.as_usize() - segment.v_origin;

		if addr >= segment.size {
			cold_path();
			return Err(Exception::ACCESS_FAULT);
		}

		addr += segment.p_origin;

		Ok(addr)
	}

	/// Loads a byte from memory.
	///
	/// # Errors
	///
	/// A segmentation fault is returned if the address is unimplemented.
	#[inline]
	pub fn load_byte(&self, addr: Long) -> Result<Byte, Exception> {
		let offset = Self::map_addr(addr)?;

		// NOTE: Always aligned!

		let value = unsafe {
			self.as_ptr()
				.cast::<Byte>()
				.add(offset)
				.read()
		};

		Ok(value)
	}

	/// Loads a word from memory.
	///
	/// # Errors
	///
	/// A segmentation fault is returned if the address is unaligned or unimplemented.
	#[inline]
	pub fn load_word(&self, addr: Long) -> Result<Word, Exception> {
		let offset = Self::map_addr(addr)?
			.shr_exact(1)
			.ok_or(Exception::ADDRESS_ERROR)?;

		let mut value = unsafe {
			self.as_ptr()
				.cast::<Word>()
				.add(offset)
				.read()
		};

		value = Word::from_be(value);

		Ok(value)
	}

	/// Loads a long-word from memory.
	///
	/// # Errors
	///
	/// A segmentation fault is returned if the address unaligned or unimplemented.
	#[inline]
	pub fn load_long(&self, addr: Long) -> Result<Long, Exception> {
		let offset = Self::map_addr(addr)?
			.shr_exact(2)
			.ok_or(Exception::ADDRESS_ERROR)?;

		let mut value = unsafe {
			self.as_ptr()
				.cast::<Long>()
				.add(offset)
				.read()
		};

		value = Long::from_be(value);

		Ok(value)
	}

	/// Stores a byte into memory.
	///
	/// # Errors
	///
	/// A segmentation fault is returned if the address is unimplemented.
	#[inline]
	pub fn store_byte(&mut self, addr: Long, value: Byte) -> Result<(), Exception> {
		let offset = Self::map_addr(addr)?;

		// NOTE: Always aligned!

		unsafe {
			self.as_mut_ptr()
				.cast::<Byte>()
				.add(offset)
				.write(value)
		};

		Ok(())
	}

	/// Stores a word into memory.
	///
	/// # Errors
	///
	/// A segmentation fault is returned if the address unaligned or unimplemented.
	#[inline]
	pub fn store_word(&mut self, addr: Long, mut value: Word) -> Result<(), Exception> {
		let offset = Self::map_addr(addr)?
			.shr_exact(1)
			.ok_or(Exception::ADDRESS_ERROR)?;

		value = value.to_be();

		unsafe {
			self.as_mut_ptr()
				.cast::<Word>()
				.add(offset)
				.write(value)
		};

		Ok(())
	}

	/// Stores a long-word into memory.
	///
	/// # Errors
	///
	/// A segmentation fault is returned if the address unaligned or unimplemented.
	#[inline]
	pub fn store_long(&mut self, addr: Long, mut value: Long) -> Result<(), Exception> {
		let offset = Self::map_addr(addr)?
			.shr_exact(2)
			.ok_or(Exception::ADDRESS_ERROR)?;

		value = value.to_be();

		unsafe {
			self.as_mut_ptr()
				.cast::<Long>()
				.add(offset)
				.write(value)
		};

		Ok(())
	}

 	/// Borrows the raw buffer.
	#[inline]
	#[must_use]
	pub fn raw(&self) -> &[u8] {
		let data = self.as_ptr().cast::<u8>();
		unsafe { slice::from_raw_parts(data, Self::DATA_SIZE) }
	}

	/// Mutably borrows the raw buffer.
	#[inline]
	#[must_use]
	pub fn raw_mut(&mut self) -> &mut [u8] {
		let data = self.as_mut_ptr().cast::<u8>();
		unsafe { slice::from_raw_parts_mut(data, Self::DATA_SIZE) }
	}

	/// Borrows the raw buffer as a byte slice.
	#[inline]
	#[must_use]
	pub fn as_bytes(&self) -> &[Byte] {
		let data = self.as_ptr().cast::<Byte>();
		unsafe { slice::from_raw_parts(data, Self::DATA_SIZE) }
	}

	/// Borrows the raw buffer as a word slice.
	#[inline]
	#[must_use]
	pub fn as_words(&self) -> &[Word] {
		const LEN: usize = Mem::DATA_SIZE >> 1;

		let data = self.as_ptr().cast::<Word>();
		unsafe { slice::from_raw_parts(data, LEN) }
	}

	/// Borrows the raw buffer as a long-word slice.
	#[inline]
	#[must_use]
	pub fn as_longs(&self) -> &[Long] {
		const LEN: usize = Mem::DATA_SIZE >> 2;

		let data = self.as_ptr().cast::<Long>();
		unsafe { slice::from_raw_parts(data, LEN) }
	}

	/// Retrieves a pointer to the raw buffer.
	#[inline]
	#[must_use]
	pub fn as_ptr(&self) -> *const () {
		self.data.as_ptr().cast::<()>()
	}

	/// Retrieves a mutable pointer to the raw buffer.
	#[inline]
	#[must_use]
	pub fn as_mut_ptr(&mut self) -> *mut () {
		self.data.as_mut_ptr().cast::<()>()
	}
}

impl Default for Mem {
	#[inline]
	fn default() -> Self {
		Self::new()
	}
}