Skip to content

Commit

Permalink
MarkSweep: fixes and more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nineonine committed Oct 6, 2023
1 parent 9368e88 commit fba3a6b
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 40 deletions.
86 changes: 83 additions & 3 deletions src/allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ impl Allocator {
Allocator { alignment }
}

pub fn allocate(&self, heap: &mut Heap, object: Object) -> Result<ObjAddr, VMError> {
pub fn allocate(
&self,
heap: &mut Heap,
object: Object,
is_root: bool,
) -> Result<ObjAddr, VMError> {
let size = object.size();

if let Some(aligned_start) = self.find_suitable_free_block(heap, size) {
heap.objects.insert(aligned_start, object);
heap.roots.insert(aligned_start);
if is_root {
heap.roots.insert(aligned_start);
}
Ok(aligned_start)
} else {
Err(VMError::AllocationError)
Expand Down Expand Up @@ -79,7 +86,7 @@ impl Allocator {
mod tests {
use std::collections::{BTreeMap, BTreeSet};

use crate::heap::MemoryCell;
use crate::{heap::MemoryCell, object::Field};

use super::*;

Expand Down Expand Up @@ -214,4 +221,77 @@ mod tests {
assert_eq!(allocator.aligned_position(i), i);
}
}

#[test]
fn test_allocate_with_sufficient_space() {
let mut heap = create_heap_with_free_list(vec![(0, 4)]);
let allocator = Allocator { alignment: 2 };
let object = Object::new(vec![
Field::new_scalar(1),
Field::new_scalar(2),
Field::new_scalar(3),
]);

let result = allocator.allocate(&mut heap, object, true);
assert!(result.is_ok());
assert_eq!(heap.objects.len(), 1); // The object should be added to `heap.objects`.
assert_eq!(heap.roots.len(), 1); // Since it's marked as root, it should be added to `heap.roots`.
assert_eq!(heap.free_list, vec![(3, 1)]); // 1 remaining cell after allocation.
}

#[test]
fn test_allocate_without_sufficient_space() {
let mut heap = create_heap_with_free_list(vec![(0, 2)]);
let allocator = Allocator { alignment: 2 };
let object = Object::new(vec![
Field::new_scalar(1),
Field::new_scalar(2),
Field::new_scalar(3),
]);

let result = allocator.allocate(&mut heap, object, true);
assert!(result.is_err()); // Allocation should fail.
assert_eq!(heap.objects.len(), 0); // No object should be added.
assert_eq!(heap.free_list, vec![(0, 2)]); // Free list should remain unchanged.
}

#[test]
fn test_allocate_multiple_objects() {
let mut heap = create_heap_with_free_list(vec![(0, 10)]);
let allocator = Allocator { alignment: 2 };

let object1 = Object::new(vec![Field::new_scalar(1), Field::new_scalar(2)]);
let object2 = Object::new(vec![
Field::new_scalar(3),
Field::new_scalar(4),
Field::new_scalar(5),
]);
let object3 = Object::new(vec![Field::new_scalar(6), Field::new_scalar(7)]);

allocator.allocate(&mut heap, object1, true).unwrap();
allocator.allocate(&mut heap, object2, true).unwrap();
allocator.allocate(&mut heap, object3, false).unwrap();

assert_eq!(heap.objects.len(), 3); // Three objects should be added.
assert_eq!(heap.roots.len(), 2); // Only two objects were marked as roots.
// Free list should have remaining spaces depending on the sizes and alignment of the objects.
}

#[test]
fn test_allocate_after_deallocate() {
let mut heap = create_heap_with_free_list(vec![(0, 10)]);
let allocator = Allocator { alignment: 2 };

let object1 = Object::new(vec![Field::new_scalar(1), Field::new_scalar(2)]);
let addr1 = allocator.allocate(&mut heap, object1, true).unwrap();

heap.deallocate(addr1).unwrap();

let object2 = Object::new(vec![Field::new_scalar(3), Field::new_scalar(4)]);
let addr2 = allocator.allocate(&mut heap, object2, false).unwrap();

assert_eq!(addr1, addr2); // Should allocate in the same spot as the deallocated object.
assert_eq!(heap.objects.len(), 1);
assert_eq!(heap.roots.len(), 0);
}
}
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::fmt;
#[derive(Debug)]
pub enum VMError {
AllocationError,
DeallocationError,
SegmentationFault,
NullPointerException(String),
GCError,
Expand All @@ -13,6 +14,7 @@ impl fmt::Display for VMError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
VMError::AllocationError => write!(f, "Allocation error"),
VMError::DeallocationError => write!(f, "Deallocation error"),
VMError::SegmentationFault => write!(f, "Segmentation fault"),
VMError::NullPointerException(detail) => {
write!(f, "Null pointer exception: {detail}")
Expand Down
45 changes: 45 additions & 0 deletions src/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,49 @@ impl Heap {
pub fn merge_free_ranges(&mut self) {
self.free_list = merge_free_list(self.free_list.to_vec());
}

pub fn deallocate(&mut self, addr: ObjAddr) -> Result<(), VMError> {
if let Some(object) = self.objects.remove(&addr) {
let size = object.size();

// Add the deallocated space back to free_list and sort it by address
self.free_list.push((addr, size));
self.free_list.sort_by(|a, b| a.0.cmp(&b.0));

// Merge adjacent free blocks
let mut i = 0;
while i < self.free_list.len() - 1 {
if self.free_list[i].0 + self.free_list[i].1 == self.free_list[i + 1].0 {
let new_size = self.free_list[i].1 + self.free_list[i + 1].1;
self.free_list[i].1 = new_size;
self.free_list.remove(i + 1);
} else {
i += 1;
}
}

// Remove the deallocated object address from the roots set, if present
self.roots.remove(&addr);
Ok(())
} else {
Err(VMError::DeallocationError) // Error type for failed deallocation
}
}

pub fn redraw_memory(&mut self) {
// Reset all memory cells to Free
for cell in &mut self.memory {
cell.status = CellStatus::Free;
}

// Set the memory cells occupied by objects to Allocated
for (addr, object) in &self.objects {
let size = object.size();
for offset in 0..size {
if let Some(cell) = self.memory.get_mut(*addr + offset) {
cell.status = CellStatus::Allocated;
}
}
}
}
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,5 +143,7 @@ fn load_program(session: &mut Session, file_name: Option<String>) -> Result<(),
session.program = program;
session.vm.reset_heap(rts_cfg.heap_size);
session.rts_cfg = rts_cfg;
session.vm.allocator.alignment = session.rts_cfg.alignment;
assert!(session.rts_cfg.alignment == session.vm.allocator.alignment);
Ok(())
}
13 changes: 13 additions & 0 deletions src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ impl Object {
self.fields.len()
}

pub fn new(fields: Vec<Field>) -> Self {
Self {
header: ObjHeader { marked: false },
fields,
}
}

pub fn random() -> Object {
let mut rng = rand::thread_rng();

Expand Down Expand Up @@ -101,6 +108,12 @@ pub enum Field {
Scalar { value: Value },
}

impl Field {
pub fn new_scalar(value: usize) -> Self {
Field::Scalar { value }
}
}

impl Serialize for Field {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
Expand Down
69 changes: 65 additions & 4 deletions src/program.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::collections::VecDeque;
use std::{collections::VecDeque, fmt};

use serde::{Deserialize, Serialize};
use serde::{
de::{self, MapAccess, Visitor},
Deserialize, Deserializer, Serialize,
};

use crate::{
gc::stats::GCStats,
Expand All @@ -9,15 +12,73 @@ use crate::{

pub type Program = VecDeque<Instruction>;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "_type")]
pub enum Instruction {
Allocate { object: Object },
Allocate { object: Object, is_root: bool },
Read { addr: usize },
Write { addr: usize, value: Value },
GC,
}

impl<'de> Deserialize<'de> for Instruction {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct InstructionVisitor;

impl<'de> Visitor<'de> for InstructionVisitor {
type Value = Instruction;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Instruction")
}

fn visit_map<V>(self, mut map: V) -> Result<Instruction, V::Error>
where
V: MapAccess<'de>,
{
let mut _type: Option<String> = None;
let mut object: Option<Object> = None;
let mut addr: Option<usize> = None;
let mut value: Option<Value> = None;
let mut is_root: Option<bool> = None;

while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"_type" => _type = map.next_value()?,
"object" => object = map.next_value()?,
"addr" => addr = map.next_value()?,
"value" => value = map.next_value()?,
"is_root" => is_root = map.next_value()?,
_ => {}
}
}

match _type.as_deref() {
Some("Allocate") => Ok(Instruction::Allocate {
object: object.ok_or_else(|| de::Error::missing_field("object"))?,
is_root: is_root.unwrap_or(true),
}),
Some("Read") => Ok(Instruction::Read {
addr: addr.ok_or_else(|| de::Error::missing_field("addr"))?,
}),
Some("Write") => Ok(Instruction::Write {
addr: addr.ok_or_else(|| de::Error::missing_field("addr"))?,
value: value.ok_or_else(|| de::Error::missing_field("value"))?,
}),
Some("GC") => Ok(Instruction::GC),
_ => Err(de::Error::custom("Invalid instruction type")),
}
}
}

const FIELDS: &[&str] = &["_type", "object", "addr", "value", "is_root"];
deserializer.deserialize_struct("Instruction", FIELDS, InstructionVisitor)
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "_type")]
pub enum InstrResult {
Expand Down
4 changes: 3 additions & 1 deletion src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,13 @@ impl Session {
}

/// program interpretation step
/// this builds event log entry and updates the visual aspect of heap
pub fn tick(&mut self) -> Result<InstrResult, VMError> {
if let Some(instruction) = self.program.get(self.instr_ptr) {
match self.vm.tick(instruction) {
Ok(instr_result) => {
match &instr_result {
InstrResult::Allocate { object, addr } => {
InstrResult::Allocate { object, addr, .. } => {
self.enqueue_log(Log::new(
format!("{object} at 0x{addr:X}"),
LogSource::ALLOC,
Expand Down Expand Up @@ -107,6 +108,7 @@ impl Session {
LogSource::GC,
Some(self.instr_ptr),
));
self.vm.heap.redraw_memory();
}
}
self.instr_ptr += 1;
Expand Down
9 changes: 7 additions & 2 deletions src/simulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,17 @@ impl Simulator {
fn gen_allocate(&mut self) -> Instruction {
// Generate a random Object
let object = Object::random();
// TODO: hook up is_root generation
let is_root = true;
match self
.vm
.allocator
.allocate(&mut self.vm.heap, object.clone())
.allocate(&mut self.vm.heap, object.clone(), is_root)
{
Ok(_) => Instruction::Allocate { object },
Ok(_) => Instruction::Allocate {
object,
is_root: true,
},
Err(_) => panic!("gen_allocate"),
}
}
Expand Down
15 changes: 7 additions & 8 deletions src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,13 @@ impl VirtualMachine {

pub fn tick(&mut self, instr: &Instruction) -> Result<InstrResult, VMError> {
match instr {
Allocate { object } => {
self.allocator
.allocate(&mut self.heap, object.clone())
.map(|addr| InstrResult::Allocate {
object: object.clone(),
addr,
})
}
Allocate { object, is_root } => self
.allocator
.allocate(&mut self.heap, object.clone(), *is_root)
.map(|addr| InstrResult::Allocate {
object: object.clone(),
addr,
}),
Read { addr } => self
.mutator
.read(&self.heap, *addr)
Expand Down
Loading

0 comments on commit fba3a6b

Please sign in to comment.