Skip to content

LONG-TERM: Redesign memory system with elegant 3-component architecture #116

@avrabe

Description

@avrabe

Problem

The current memory system violates every principle of elegant design:

  • Single Responsibility: CapabilityAwareProvider does capability checking AND memory management
  • Composition: Complex factory hierarchies instead of simple composition
  • No Circular Dependencies: Provider creation can trigger module creation
  • Minimal Global State: Heavy reliance on global capability context
  • Type Safety: Type mismatches between different provider types
  • Clear Error Handling: "Generated error" masks real issues
  • Performance: Multiple layers of indirection and global lookups

Current Architecture (5+ layers)

NoStdProvider → CapabilityAwareProvider → RuntimeProvider → MemoryCapabilityContext → Factory patterns

Each layer adds complexity, potential circular dependencies, and failure points.

Proposed Elegant Architecture (3 components)

Component 1: MemoryBlock (replaces NoStdProvider)

struct MemoryBlock<const N: usize> {
    data: [u8; N],
    used: AtomicUsize,
}

impl<const N: usize> MemoryBlock<N> {
    pub fn new() -> Self { /* simple constructor */ }
    pub fn allocate(&self, size: usize) -> Option<&mut [u8]> { /* atomic allocation */ }
    pub fn deallocate(&self, ptr: *mut u8, size: usize) { /* atomic deallocation */ }
}
  • Single responsibility: Just raw memory management
  • No capability logic: Pure memory operations
  • No global dependencies: Self-contained

Component 2: BudgetTracker (replaces CapabilityContext)

struct BudgetTracker {
    budgets: [AtomicUsize; NUM_CRATES], // Static array, no heap allocation
}

impl BudgetTracker {
    pub fn allocate(&self, crate_id: CrateId, size: usize) -> Result<()> {
        let budget = &self.budgets[crate_id as usize];
        let remaining = budget.fetch_sub(size, Ordering::AcqRel);
        if remaining < size {
            budget.fetch_add(size, Ordering::AcqRel); // Return budget
            return Err(Error::budget_exceeded());
        }
        Ok(())
    }
    
    pub fn deallocate(&self, crate_id: CrateId, size: usize) {
        self.budgets[crate_id as usize].fetch_add(size, Ordering::AcqRel);
    }
}
  • Single responsibility: Only budget tracking
  • Thread-safe: Atomic operations
  • No memory management: Pure accounting

Component 3: ManagedMemory (replaces CapabilityAwareProvider)

struct ManagedMemory<const N: usize> {
    block: MemoryBlock<N>,
    budget_tracker: &'static BudgetTracker,
    crate_id: CrateId,
}

impl<const N: usize> ManagedMemory<N> {
    pub fn new(crate_id: CrateId) -> Result<Self> {
        GLOBAL_BUDGET_TRACKER.allocate(crate_id, N)?;
        Ok(Self {
            block: MemoryBlock::new(),
            budget_tracker: &GLOBAL_BUDGET_TRACKER,
            crate_id,
        })
    }
}

impl<const N: usize> Drop for ManagedMemory<N> {
    fn drop(&mut self) {
        self.budget_tracker.deallocate(self.crate_id, N);
    }
}
  • Composition: Combines MemoryBlock + BudgetTracker
  • RAII: Automatic budget cleanup
  • Simple interface: Clean abstractions

Single Allocation API (replaces all factory complexity)

pub fn allocate_memory<const N: usize>(crate_id: CrateId) -> Result<ManagedMemory<N>> {
    ManagedMemory::<N>::new(crate_id)
}

// Usage:
let provider = allocate_memory::<32768>(CrateId::Runtime)?;
let mut vec = BoundedVec::new(provider)?;

Expected Benefits

  • 70% reduction in complexity: 3 simple components vs 5+ complex layers
  • No circular dependencies: Clear, linear dependency flow
  • Consistent performance: No global context lookups or factory overhead
  • Better error handling: Each component has clear failure modes
  • Easier maintenance: Simple, understandable code
  • Better testing: Each component can be tested in isolation
  • Cross-platform consistency: No platform-specific workarounds needed

Migration Strategy

Phase 1: Implement new components alongside existing system

  • Add MemoryBlock, BudgetTracker, ManagedMemory types
  • Keep old API as wrapper around new system
  • Add comprehensive tests for new components

Phase 2: Migrate core modules

  • Update Module, BoundedVec, BoundedMap to use new system
  • Maintain API compatibility where possible
  • Performance and correctness testing

Phase 3: Remove legacy system

  • Remove old provider types and factory patterns
  • Clean up unused code and dependencies
  • Update documentation

Files Affected

New files:

  • wrt-foundation/src/memory/memory_block.rs
  • wrt-foundation/src/memory/budget_tracker.rs
  • wrt-foundation/src/memory/managed_memory.rs
  • wrt-foundation/src/memory/mod.rs

Modified files:

  • wrt-foundation/src/lib.rs (new public API)
  • wrt-foundation/src/bounded.rs (use new providers)
  • wrt-runtime/src/module.rs (simplified initialization)
  • All files using safe_managed_alloc! macro

Priority

MEDIUM - Important for long-term maintainability, but not blocking current functionality

Estimated Time

1-2 weeks (major architectural change, needs careful migration and testing)

Dependencies

Success Criteria

  • 70%+ reduction in memory system code complexity
  • No performance regression in allocation/deallocation
  • All existing functionality preserved
  • Cross-platform compatibility maintained
  • Comprehensive test coverage for new architecture
  • Documentation updated to reflect new design

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions