Skip to content

Commit

Permalink
[SCD/32X] when loading save states, deserialize large arrays directly…
Browse files Browse the repository at this point in the history
… into heap memory instead of relying on bincode's derive implementation

the derive implementation appears to deserialize into stack memory and then move to the heap; this is particularly problematic for Sega CD which has more than 1MB of emulated memory between the Genesis hardware and the Sega CD hardware
  • Loading branch information
jsgroth committed Sep 28, 2024
1 parent e63d6fe commit 7aba1f5
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 8 deletions.
5 changes: 3 additions & 2 deletions backend/s32x-core/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::registers::SystemRegisters;
use crate::vdp::Vdp;
use bincode::{Decode, Encode};
use genesis_core::{GenesisRegion, timing};
use jgenesis_common::boxedarray::BoxedWordArray;
use jgenesis_common::frontend::TimingMode;
use jgenesis_proc_macros::PartialClone;
use sh2_emu::Sh2;
Expand Down Expand Up @@ -46,7 +47,7 @@ pub struct Sega32X {
pub pwm: PwmChip,
pub registers: SystemRegisters,
pub m68k_vectors: Box<M68kVectors>,
pub sdram: Box<Sdram>,
pub sdram: BoxedWordArray<SDRAM_LEN_WORDS>,
pub serial: SerialInterface,
pub region: GenesisRegion,
}
Expand All @@ -73,7 +74,7 @@ impl Sega32X {
pwm: PwmChip::new(timing_mode),
registers: SystemRegisters::new(),
m68k_vectors: bootrom::M68K_VECTORS.to_vec().into_boxed_slice().try_into().unwrap(),
sdram: vec![0; SDRAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(),
sdram: BoxedWordArray::new(),
serial: SerialInterface::default(),
region,
}
Expand Down
9 changes: 5 additions & 4 deletions backend/segacd-core/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use cdrom::cdtime::CdTime;
use cdrom::reader::{CdRom, CdRomFileFormat};
use genesis_core::GenesisRegion;
use genesis_core::memory::{Memory, PhysicalMedium};
use jgenesis_common::boxedarray::BoxedByteArray;
use jgenesis_common::num::{GetBit, U16Ext};
use jgenesis_proc_macros::{FakeDecode, FakeEncode, PartialClone};
use m68000_emu::BusInterface;
Expand Down Expand Up @@ -132,11 +133,11 @@ pub struct SegaCd {
bios: Bios,
#[partial_clone(partial)]
disc_drive: CdController,
prg_ram: Box<[u8; PRG_RAM_LEN]>,
prg_ram: BoxedByteArray<PRG_RAM_LEN>,
word_ram: WordRam,
backup_ram: Box<[u8; BACKUP_RAM_LEN]>,
enable_ram_cartridge: bool,
ram_cartridge: Box<[u8; RAM_CARTRIDGE_LEN]>,
ram_cartridge: BoxedByteArray<RAM_CARTRIDGE_LEN>,
ram_cartridge_writes_enabled: bool,
backup_ram_dirty: bool,
registers: SegaCdRegisters,
Expand Down Expand Up @@ -173,11 +174,11 @@ impl SegaCd {
Ok(Self {
bios: Bios(bios),
disc_drive: CdController::new(disc),
prg_ram: vec![0; PRG_RAM_LEN].into_boxed_slice().try_into().unwrap(),
prg_ram: BoxedByteArray::new(),
word_ram: WordRam::new(),
backup_ram,
enable_ram_cartridge,
ram_cartridge,
ram_cartridge: ram_cartridge.into(),
ram_cartridge_writes_enabled: true,
backup_ram_dirty: false,
registers: SegaCdRegisters::new(),
Expand Down
5 changes: 3 additions & 2 deletions backend/segacd-core/src/memory/wordram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::memory::ScdCpu;
use bincode::{Decode, Encode};
use jgenesis_common::boxedarray::BoxedByteArray;
use jgenesis_common::num::GetBit;

// Word RAM is 256KB
Expand Down Expand Up @@ -75,7 +76,7 @@ enum WordRamSubMapResult {

#[derive(Debug, Clone, Encode, Decode)]
pub struct WordRam {
ram: Box<[u8; WORD_RAM_LEN]>,
ram: BoxedByteArray<WORD_RAM_LEN>,
sub_buffered_writes: Vec<(u32, u8)>,
sub_blocked_read: bool,
mode: WordRamMode,
Expand All @@ -96,7 +97,7 @@ const CELL_IMAGE_H_SIZE_BYTES: u32 = 64 * 8 / 2;
impl WordRam {
pub fn new() -> Self {
Self {
ram: vec![0; WORD_RAM_LEN].into_boxed_slice().try_into().unwrap(),
ram: BoxedByteArray::new(),
sub_buffered_writes: Vec::with_capacity(5),
sub_blocked_read: false,
mode: WordRamMode::default(),
Expand Down
126 changes: 126 additions & 0 deletions common/jgenesis-common/src/boxedarray.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//! Wrappers around `Box<[u8; LEN]>` and `Box<[u16; LEN]>` with a custom `bincode::Decode`
//! implementation that deserializes directly into heap memory.
//!
//! This exists because the implementation that `#[derive(Decode)]` generates for `Box<[u8; LEN]>`
//! deserializes into stack memory and then moves to the heap, which is problematic when deserializing
//! large arrays (particularly on Windows).
use bincode::de::read::Reader;
use bincode::de::{BorrowDecoder, Decoder};
use bincode::error::DecodeError;
use bincode::{BorrowDecode, Decode, Encode};
use std::ops::{Deref, DerefMut};

#[derive(Debug, Clone, Encode)]
pub struct BoxedByteArray<const LEN: usize>(Box<[u8; LEN]>);

impl<const LEN: usize> BoxedByteArray<LEN> {
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn new() -> Self {
Self(vec![0; LEN].into_boxed_slice().try_into().unwrap())
}
}

impl<const LEN: usize> Default for BoxedByteArray<LEN> {
fn default() -> Self {
Self::new()
}
}

impl<const LEN: usize> From<Box<[u8; LEN]>> for BoxedByteArray<LEN> {
fn from(value: Box<[u8; LEN]>) -> Self {
Self(value)
}
}

impl<const LEN: usize> Deref for BoxedByteArray<LEN> {
type Target = Box<[u8; LEN]>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<const LEN: usize> DerefMut for BoxedByteArray<LEN> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl<const LEN: usize> Decode for BoxedByteArray<LEN> {
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
let mut array: Box<[u8; LEN]> = vec![0; LEN].into_boxed_slice().try_into().unwrap();
decoder.reader().read(array.as_mut())?;
Ok(Self(array))
}
}

impl<'de, const LEN: usize> BorrowDecode<'de> for BoxedByteArray<LEN> {
fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
let mut array: Box<[u8; LEN]> = vec![0; LEN].into_boxed_slice().try_into().unwrap();
decoder.reader().read(array.as_mut())?;
Ok(Self(array))
}
}

#[derive(Debug, Clone, Encode)]
pub struct BoxedWordArray<const LEN: usize>(Box<[u16; LEN]>);

impl<const LEN: usize> Default for BoxedWordArray<LEN> {
fn default() -> Self {
Self::new()
}
}

impl<const LEN: usize> BoxedWordArray<LEN> {
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn new() -> Self {
Self(vec![0; LEN].into_boxed_slice().try_into().unwrap())
}
}

impl<const LEN: usize> From<Box<[u16; LEN]>> for BoxedWordArray<LEN> {
fn from(value: Box<[u16; LEN]>) -> Self {
Self(value)
}
}

impl<const LEN: usize> Deref for BoxedWordArray<LEN> {
type Target = Box<[u16; LEN]>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<const LEN: usize> DerefMut for BoxedWordArray<LEN> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl<const LEN: usize> Decode for BoxedWordArray<LEN> {
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
let mut array: Box<[u16; LEN]> = vec![0; LEN].into_boxed_slice().try_into().unwrap();

for value in array.as_mut() {
*value = u16::decode(decoder)?;
}

Ok(Self(array))
}
}

impl<'de, const LEN: usize> BorrowDecode<'de> for BoxedWordArray<LEN> {
fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
let mut array: Box<[u16; LEN]> = vec![0; LEN].into_boxed_slice().try_into().unwrap();

for value in array.as_mut() {
*value = u16::decode(decoder)?;
}

Ok(Self(array))
}
}
1 change: 1 addition & 0 deletions common/jgenesis-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod audio;
pub mod boxedarray;
pub mod frontend;
pub mod input;
pub mod num;
Expand Down

0 comments on commit 7aba1f5

Please sign in to comment.