abi/memory/
mod.rs

1//! # Memory Management Module
2//!
3//! This module provides low-level memory management functionality for the Xila operating system.
4//! It exposes a C-compatible ABI for memory allocation, deallocation, and memory operations
5//! that can be used by applications and other system components.
6//!
7//! ## Features
8//!
9//! - **Memory Allocation**: Allocate memory blocks with specific size, alignment, and capabilities
10//! - **Memory Deallocation**: Free allocated memory blocks
11//! - **Memory Reallocation**: Resize existing memory blocks
12//! - **Cache Management**: Flush data and instruction caches
13//! - **Memory Protection**: Support for read, write, and execute permissions
14//! - **Memory Capabilities**: Support for executable memory and DMA-capable memory
15//!
16//! ## Safety
17//!
18//! This module provides unsafe functions that directly manipulate memory. Callers must ensure:
19//! - Pointers are valid and properly aligned
20//! - Memory is not accessed after deallocation
21//! - Concurrent access is properly synchronized
22//!
23//! ## Example Usage
24//!
25//! ```c
26//! // Allocate 1024 bytes with 8-byte alignment
27//! void* ptr = xila_memory_allocate(NULL, 1024, 8, 0);
28//! if (ptr != NULL) {
29//!     // Use the memory...
30//!     xila_memory_deallocate(ptr);
31//! }
32//! ```
33
34#![allow(dead_code)]
35
36use alloc::collections::BTreeMap;
37use core::ptr::null_mut;
38use core::{ffi::c_void, ptr::NonNull};
39use futures::block_on;
40use log::{Debug, Trace, Warning};
41use synchronization::blocking_mutex::raw::CriticalSectionRawMutex;
42
43use synchronization::rwlock::RwLock;
44
45use memory::{Capabilities, Layout};
46
47/// Memory protection flags that can be combined using bitwise OR.
48/// These flags determine what operations are allowed on memory regions.
49pub type XilaMemoryProtection = u8;
50
51/// Read permission flag - allows reading from memory
52#[unsafe(no_mangle)]
53pub static XILA_MEMORY_PROTECTION_READ: u8 = memory::Protection::READ_BIT;
54
55/// Write permission flag - allows writing to memory
56#[unsafe(no_mangle)]
57pub static XILA_MEMORY_PROTECTION_WRITE: u8 = memory::Protection::WRITE_BIT;
58
59/// Execute permission flag - allows executing code from memory
60#[unsafe(no_mangle)]
61pub static XILA_MEMORY_PROTECTION_EXECUTE: u8 = memory::Protection::EXECUTE_BIT;
62
63/// Memory capability flags that specify special requirements for allocated memory.
64/// These flags can be combined using bitwise OR to request multiple capabilities.
65pub type XilaMemoryCapabilities = u8;
66
67/// Executable capability - memory can be used for code execution
68#[unsafe(no_mangle)]
69pub static XILA_MEMORY_CAPABILITIES_EXECUTE: XilaMemoryCapabilities =
70    memory::Capabilities::EXECUTABLE_FLAG;
71
72/// Direct Memory Access (DMA) capability - memory is accessible by DMA controllers
73#[unsafe(no_mangle)]
74pub static XILA_MEMORY_CAPABILITIES_DIRECT_MEMORY_ACCESS: XilaMemoryCapabilities =
75    memory::Capabilities::DIRECT_MEMORY_ACCESS_FLAG;
76
77/// No special capabilities required - standard memory allocation
78#[unsafe(no_mangle)]
79pub static XILA_MEMORY_CAPABILITIES_NONE: XilaMemoryCapabilities = 0;
80
81// - Memory Management Functions
82
83// - - Allocation Utilities
84
85/// Converts a function that returns an `Option<NonNull<P>>` into a raw C-compatible pointer.
86///
87/// This utility function is used internally to convert Rust's safe pointer types
88/// into C-compatible raw pointers. Returns `null_mut()` if the function returns `None`.
89///
90/// # Type Parameters
91///
92/// * `F` - A function that returns `Option<NonNull<P>>`
93/// * `P` - The pointer type being converted
94///
95/// # Parameters
96///
97/// * `Function` - The function to execute and convert the result
98///
99/// # Returns
100///
101/// A raw C-compatible pointer, or null if the function returns `None`
102pub fn into_pointer<F, P>(function: F) -> *mut c_void
103where
104    F: FnOnce() -> Option<NonNull<P>>,
105{
106    match function() {
107        Some(pointer) => pointer.as_ptr() as *mut c_void,
108        None => null_mut(),
109    }
110}
111
112/// Global allocation tracking table.
113///
114/// This table maps memory addresses to their corresponding memory layouts,
115/// enabling proper deallocation and reallocation operations. The table is
116/// protected by a RwLock to ensure thread safety.
117static ALLOCATIONS_TABLE: RwLock<CriticalSectionRawMutex, BTreeMap<usize, Layout>> =
118    RwLock::new(BTreeMap::new());
119
120/// Macro to acquire a write lock on the allocations table.
121///
122/// This macro blocks until the lock is acquired, ensuring exclusive access
123/// to the allocation tracking table for modification operations.
124macro_rules! Write_allocations_table {
125    () => {
126        block_on(ALLOCATIONS_TABLE.write())
127    };
128}
129
130/// Deallocates a previously allocated memory block.
131///
132/// This function frees memory that was previously allocated using `xila_memory_allocate`
133/// or `xila_memory_reallocate`. It's safe to call this function with a null pointer.
134///
135/// # Safety
136///
137/// - The pointer must have been returned by a previous call to `xila_memory_allocate`
138///   or `xila_memory_reallocate`
139/// - The pointer must not be used after this function returns
140/// - Double-free is safe and will be ignored
141///
142/// # Parameters
143///
144/// * `Pointer` - Pointer to the memory block to deallocate, or null
145///
146/// # Examples
147///
148/// ```c
149/// void* ptr = xila_memory_allocate(NULL, 1024, 8, 0);
150/// xila_memory_deallocate(ptr); // Free the memory
151/// xila_memory_deallocate(NULL); // Safe - ignored
152/// ```
153#[unsafe(no_mangle)]
154pub extern "C" fn xila_memory_deallocate(pointer: *mut c_void) {
155    if pointer.is_null() {
156        Warning! { "xila_memory_deallocate called with null pointer, ignoring"
157        };
158        return;
159    }
160
161    let layout = match Write_allocations_table!().remove(&(pointer as usize)) {
162        Some(size) => size,
163        None => {
164            Warning! {
165                "xila_memory_deallocate called with untracked pointer: {:#x}, ignoring",
166                pointer as usize
167            };
168            return;
169        }
170    };
171
172    unsafe {
173        memory::get_instance().deallocate(
174            NonNull::new(pointer as *mut u8).expect("Failed to deallocate memory, pointer is null"),
175            layout,
176        );
177        Debug! {
178            "xila_memory_deallocate called with pointer: {:#x}, deallocated successfully",
179            pointer as usize
180        };
181    }
182}
183
184/// Reallocates a memory block to a new size.
185///
186/// This function changes the size of a previously allocated memory block. If the new size
187/// is larger, the additional memory is uninitialized. If the new size is smaller, the
188/// excess memory is freed. The original data is preserved up to the minimum of the old
189/// and new sizes.
190///
191/// # Safety
192///
193/// - If `Pointer` is not null, it must have been returned by a previous call to
194///   `xila_memory_allocate` or `xila_memory_reallocate`
195/// - The pointer must not be used after this function returns (use the returned pointer instead)
196/// - If the function returns null, the original pointer remains valid
197///
198/// # Parameters
199///
200/// * `Pointer` - Pointer to the memory block to reallocate, or null for new allocation
201/// * `Size` - New size in bytes (0 is equivalent to deallocation)
202///
203/// # Returns
204///
205/// - Pointer to the reallocated memory block
206/// - Null if allocation fails or if size is 0
207/// - If `Pointer` is null, behaves like `xila_memory_allocate` with default alignment
208///
209/// # Examples
210///
211/// ```c
212/// // Allocate initial memory
213/// void* ptr = xila_memory_reallocate(NULL, 1024);
214///
215/// // Expand the memory
216/// ptr = xila_memory_reallocate(ptr, 2048);
217///
218/// // Shrink the memory
219/// ptr = xila_memory_reallocate(ptr, 512);
220///
221/// // Free the memory
222/// xila_memory_reallocate(ptr, 0);
223/// ```
224#[unsafe(no_mangle)]
225pub unsafe extern "C" fn xila_memory_reallocate(pointer: *mut c_void, size: usize) -> *mut c_void {
226    unsafe {
227        into_pointer(|| {
228            let pointer = NonNull::new(pointer as *mut u8);
229
230            let mut allocation_table = block_on(ALLOCATIONS_TABLE.write());
231
232            let old_layout = match pointer {
233                None => Layout::from_size_align(size, 1)
234                    .expect("Failed to create layout for memory reallocation"),
235                Some(pointer) =>
236                // Get the layout from the allocation table using the pointer's address
237                {
238                    allocation_table
239                        .get(&(pointer.as_ptr() as usize))
240                        .cloned()?
241                }
242            };
243
244            let new_layout = Layout::from_size_align(size, old_layout.align()).ok()?;
245
246            Debug!(
247                "xila_memory_reallocate called with Pointer: {:#x}, Old_layout: {:?}, New_layout: {:?}",
248                pointer.map_or(0, |p| p.as_ptr() as usize),
249                old_layout,
250                new_layout
251            );
252
253            let allocated = memory::get_instance().reallocate(pointer, old_layout, new_layout)?;
254
255            allocation_table.insert(allocated.as_ptr() as usize, new_layout);
256
257            Some(allocated)
258        })
259    }
260}
261
262/// Allocates a memory block with specified properties.
263///
264/// This function allocates a block of memory with the specified size, alignment,
265/// and capabilities. The memory is uninitialized and must be properly initialized
266/// before use.
267///
268/// # Safety
269///
270/// This function is unsafe because:
271/// - The returned memory is uninitialized
272/// - The caller must ensure proper deallocation
273/// - The hint address, if providalignment enumed, must be a valid memory address
274///
275/// # Parameters
276///
277/// * `Hint_address` - Preferred memory address (hint only, may be ignored), or null
278/// * `Size` - Size of the memory block in bytes
279/// * `Alignment` - Required memory alignment in bytes (must be a power of 2)
280/// * `Capabilities` - Memory capabilities flags (see `xila_memory_capabilities_*` constants)
281///
282/// # Returns
283///
284/// - Pointer to the allocated memory block
285/// - Null if allocation fails
286///
287/// # Errors
288///
289/// Returns null if:
290/// - Insufficient memory available
291/// - Invalid alignment (not a power of 2)
292/// - Requested capabilities not supported
293/// - Size is too large
294///
295/// # Examples
296///
297/// ```c
298/// // Allocate 1024 bytes with 8-byte alignment
299/// void* ptr = xila_memory_allocate(NULL, 1024, 8, xila_memory_capabilities_none);
300///
301/// // Allocate executable memory for code
302/// void* code_ptr = xila_memory_allocate(NULL, 4096, 4096, XILA_MEMORY_CAPABILITIES_EXECUTE);
303///
304/// // Allocate DMA-capable memory
305/// void* dma_ptr = xila_memory_allocate(NULL, 2048, 32, xila_memory_capabilities_direct_memory_access);
306/// ```
307#[unsafe(no_mangle)]
308pub unsafe extern "C" fn xila_memory_allocate(
309    _: *mut c_void,
310    size: usize,
311    alignment: usize,
312    capabilities: XilaMemoryCapabilities,
313) -> *mut c_void {
314    unsafe {
315        into_pointer(|| {
316            Trace!(
317                "xila_memory_allocate called with Size: {size}, Alignment: {alignment}, Capabilities: {capabilities:?}"
318            );
319            let layout = Layout::from_size_align(size, alignment)
320                .expect("Failed to create layout for memory allocation");
321
322            let capabilities = Capabilities::from_u8(capabilities);
323
324            let result = memory::get_instance().allocate(capabilities, layout);
325
326            if let Some(pointer) = result {
327                Write_allocations_table!().insert(pointer.as_ptr() as usize, layout);
328                Debug! {
329                    "xila_memory_allocate called with Size: {}, Alignment: {}, Capabilities: {:?}, allocated memory at {:#x}",
330                    size, alignment, capabilities, pointer.as_ptr() as usize
331                };
332            } else {
333                Warning! {
334                    "xila_memory_allocate failed with Size: {size}, Alignment: {alignment}, Capabilities: {capabilities:?}"
335                };
336            }
337
338            result
339        })
340    }
341}
342
343/// Returns the system's memory page size.
344///
345/// The page size is the smallest unit of memory that can be allocated by the
346/// virtual memory system. This is useful for applications that need to work
347/// with page-aligned memory or perform memory-mapped I/O operations.
348///
349/// # Returns
350///
351/// The page size in bytes (typically 4096 on most systems)
352///
353/// # Examples
354///
355/// ```c
356/// size_t page_size = xila_memory_get_page_size();
357/// printf("System page size: %zu bytes\n", page_size);
358///
359/// // Allocate page-aligned memory
360/// void* ptr = xila_memory_allocate(NULL, page_size * 2, page_size, 0);
361/// ```
362#[unsafe(no_mangle)]
363pub extern "C" fn xila_memory_get_page_size() -> usize {
364    memory::get_instance().get_page_size()
365}
366
367/// Flushes the data cache.
368///
369/// This function ensures that all pending write operations in the data cache
370/// are written to main memory. This is important for cache coherency in
371/// multi-core systems or when interfacing with DMA controllers.
372///
373/// # Safety
374///
375/// This function is safe to call at any time, but may have performance implications
376/// as it forces cache synchronization.
377///
378/// # Examples
379///
380/// ```c
381/// // After writing data that will be accessed by DMA
382/// memcpy(dma_buffer, data, size);
383/// xila_memory_flush_data_cache();
384/// start_dma_transfer(dma_buffer, size);
385/// ```
386#[unsafe(no_mangle)]
387pub extern "C" fn xila_memory_flush_data_cache() {
388    memory::get_instance().flush_data_cache();
389}
390
391/// Flushes the instruction cache for a specific memory region.
392///
393/// This function invalidates the instruction cache for the specified memory region.
394/// This is necessary after modifying executable code to ensure the processor
395/// sees the updated instructions.
396///
397/// # Safety
398///
399/// - The address must point to valid memory
400/// - The memory region must not be accessed for execution during the flush operation
401/// - The size should not exceed the actual allocated memory size
402///
403/// # Parameters
404///
405/// * `Address` - Starting address of the memory region to flush
406/// * `Size` - Size of the memory region in bytes
407///
408/// # Examples
409///
410/// ```c
411/// // After writing machine code to executable memory
412/// void* code_ptr = xila_memory_allocate(NULL, 4096, 4096, XILA_MEMORY_CAPABILITIES_EXECUTE);
413/// memcpy(code_ptr, machine_code, code_size);
414/// xila_memory_flush_instruction_cache(code_ptr, code_size);
415///
416/// // Now safe to execute the code
417/// ((void(*)())code_ptr)();
418/// ```
419#[unsafe(no_mangle)]
420pub extern "C" fn xila_memory_flush_instruction_cache(_address: *mut c_void, _size: usize) {
421    let address = NonNull::new(_address as *mut u8).expect("Failed to flush instruction cache");
422
423    memory::get_instance().flush_instruction_cache(address, _size);
424}
425
426#[cfg(test)]
427mod tests {
428    //! # Memory Management Tests
429    //!
430    //! This module contains comprehensive tests for the memory management functionality.
431    //! The tests cover various scenarios including basic allocation/deallocation,
432    //! edge cases, error conditions, and stress testing.
433    //!
434    //! ## Test Categories
435    //!
436    //! - **Basic Operations**: Standard allocation, deallocation, and reallocation
437    //! - **Edge Cases**: Zero-size allocations, large alignments, null pointers
438    //! - **Capabilities**: Testing executable and DMA-capable memory
439    //! - **Cache Operations**: Data and instruction cache flushing
440    //! - **Stress Testing**: Multiple allocations and allocation tracking
441    //! - **Error Handling**: Invalid inputs and error recovery
442
443    use alloc::vec::Vec;
444
445    use super::*;
446
447    /// Tests basic memory allocation and deallocation functionality.
448    ///
449    /// This test verifies that:
450    /// - Memory can be successfully allocated with specified size and alignment
451    /// - Allocated memory is readable and writable
452    /// - Memory can be properly deallocated without errors
453    /// - Data written to memory is correctly stored and retrieved
454    #[test]
455    fn test_allocate_deallocate_basic() {
456        unsafe {
457            // Test basic allocation and deallocation
458            let size = 128;
459            let alignment = 8;
460            let capabilities = 0; // Basic capabilities
461            let hint_address = core::ptr::null_mut();
462
463            let pointer = xila_memory_allocate(hint_address, size, alignment, capabilities);
464            assert!(!pointer.is_null(), "Memory allocation should succeed");
465
466            // Write and read to verify the memory is accessible
467            let ptr = pointer as *mut u8;
468            for i in 0..size {
469                *ptr.add(i) = (i % 256) as u8;
470            }
471
472            for i in 0..size {
473                assert_eq!(
474                    *ptr.add(i),
475                    (i % 256) as u8,
476                    "Memory should be readable and writable"
477                );
478            }
479
480            // Deallocate the memory
481            xila_memory_deallocate(pointer);
482        }
483    }
484
485    /// Tests allocation behavior with zero size.
486    ///
487    /// Zero-size allocations are a special case that different allocators
488    /// may handle differently. This test ensures the implementation handles
489    /// them gracefully without crashing.
490    #[test]
491    fn test_allocate_zero_size() {
492        unsafe {
493            // Test allocation with zero size
494            let size = 0;
495            let alignment = 8;
496            let capabilities = 0;
497            let hint_address = core::ptr::null_mut();
498
499            let pointer = xila_memory_allocate(hint_address, size, alignment, capabilities);
500            // Zero-size allocation might return null or a valid pointer, both are acceptable
501            if !pointer.is_null() {
502                xila_memory_deallocate(pointer);
503            }
504        }
505    }
506
507    /// Tests memory allocation with large alignment requirements.
508    ///
509    /// This test verifies that the allocator can handle large alignment
510    /// requirements (64 bytes) and that the returned pointer is properly
511    /// aligned to the requested boundary.
512    #[test]
513    fn test_allocate_large_alignment() {
514        unsafe {
515            // Test allocation with large alignment
516            let size = 256;
517            let alignment = 64; // 64-byte alignment
518            let capabilities = 0;
519            let hint_address = core::ptr::null_mut();
520
521            let pointer = xila_memory_allocate(hint_address, size, alignment, capabilities);
522            assert!(
523                !pointer.is_null(),
524                "Large alignment allocation should succeed"
525            );
526
527            // Verify alignment
528            let addr = pointer as usize;
529            assert_eq!(addr % alignment, 0, "Pointer should be properly aligned");
530
531            xila_memory_deallocate(pointer);
532        }
533    }
534
535    /// Tests allocation of executable memory.
536    ///
537    /// This test verifies that memory can be allocated with executable
538    /// capabilities, which is necessary for just-in-time compilation
539    /// and dynamic code generation.
540    #[test]
541    fn test_allocate_executable_capability() {
542        unsafe {
543            // Test allocation with executable capability
544            let size = 128;
545            let alignment = 8;
546            let capabilities = XILA_MEMORY_CAPABILITIES_EXECUTE;
547            let hint_address = core::ptr::null_mut();
548
549            let pointer = xila_memory_allocate(hint_address, size, alignment, capabilities);
550            assert!(
551                !pointer.is_null(),
552                "Executable memory allocation should succeed"
553            );
554
555            xila_memory_deallocate(pointer);
556        }
557    }
558
559    /// Tests allocation of DMA-capable memory.
560    ///
561    /// DMA-capable memory must meet specific hardware requirements and
562    /// may need to be allocated from special memory regions. This test
563    /// is ignored by default as it requires specific hardware support.
564    #[test]
565    #[ignore = "Requires specific hardware support for DMA"]
566    fn test_allocate_dma_capability() {
567        unsafe {
568            // Test allocation with DMA capability
569            let size = 128;
570            let alignment = 8;
571            let capabilities = XILA_MEMORY_CAPABILITIES_DIRECT_MEMORY_ACCESS;
572            let hint_address = core::ptr::null_mut();
573
574            let pointer = xila_memory_allocate(hint_address, size, alignment, capabilities);
575            assert!(!pointer.is_null(), "DMA memory allocation should succeed");
576
577            xila_memory_deallocate(pointer);
578        }
579    }
580
581    /// Tests that deallocating a null pointer is safe.
582    ///
583    /// According to standard behavior, deallocating a null pointer
584    /// should be a no-op and not cause any errors or crashes.
585    #[test]
586    fn test_deallocate_null_pointer() {
587        // Test that deallocating a null pointer doesn't crash
588        xila_memory_deallocate(core::ptr::null_mut());
589    }
590
591    /// Tests reallocation from a null pointer (equivalent to allocation).
592    ///
593    /// When realloc is called with a null pointer, it should behave
594    /// identically to malloc, allocating new memory of the requested size.
595    #[test]
596    fn test_reallocate_null_to_new() {
597        unsafe {
598            // Test reallocating from null (equivalent to allocation)
599            let size = 128;
600            let pointer = xila_memory_reallocate(core::ptr::null_mut(), size);
601            assert!(
602                !pointer.is_null(),
603                "Reallocation from null should work like allocation"
604            );
605
606            // Write to the memory to verify it's usable
607            let ptr = pointer as *mut u8;
608            for i in 0..size {
609                *ptr.add(i) = (i % 256) as u8;
610            }
611
612            xila_memory_deallocate(pointer);
613        }
614    }
615
616    /// Tests expanding memory through reallocation.
617    ///
618    /// This test verifies that existing data is preserved when memory
619    /// is expanded through reallocation, and that the new memory region
620    /// is usable.
621    #[test]
622    fn test_reallocate_expand() {
623        unsafe {
624            // Test expanding memory with reallocation
625            let initial_size = 64;
626            let expanded_size = 128;
627
628            // First allocation
629            let pointer = xila_memory_reallocate(core::ptr::null_mut(), initial_size);
630            assert!(!pointer.is_null(), "Initial allocation should succeed");
631
632            // Fill with test data
633            let ptr = pointer as *mut u8;
634            for i in 0..initial_size {
635                *ptr.add(i) = (i % 256) as u8;
636            }
637
638            // Expand the allocation
639            let new_pointer = xila_memory_reallocate(pointer, expanded_size);
640            assert!(
641                !new_pointer.is_null(),
642                "Reallocation expansion should succeed"
643            );
644
645            // Verify original data is preserved
646            let new_ptr = new_pointer as *mut u8;
647            for i in 0..initial_size {
648                assert_eq!(
649                    *new_ptr.add(i),
650                    (i % 256) as u8,
651                    "Original data should be preserved"
652                );
653            }
654
655            xila_memory_deallocate(new_pointer);
656        }
657    }
658
659    /// Tests shrinking memory through reallocation.
660    ///
661    /// This test verifies that data within the new size bounds is preserved
662    /// when memory is shrunk through reallocation.
663    #[test]
664    fn test_reallocate_shrink() {
665        unsafe {
666            // Test shrinking memory with reallocation
667            let initial_size = 128;
668            let shrunk_size = 64;
669
670            // First allocation
671            let pointer = xila_memory_reallocate(core::ptr::null_mut(), initial_size);
672            assert!(!pointer.is_null(), "Initial allocation should succeed");
673
674            // Fill with test data
675            let ptr = pointer as *mut u8;
676            for i in 0..initial_size {
677                *ptr.add(i) = (i % 256) as u8;
678            }
679
680            // Shrink the allocation
681            let new_pointer = xila_memory_reallocate(pointer, shrunk_size);
682            assert!(
683                !new_pointer.is_null(),
684                "Reallocation shrinking should succeed"
685            );
686
687            // Verify data within new size is preserved
688            let new_ptr = new_pointer as *mut u8;
689            for i in 0..shrunk_size {
690                assert_eq!(
691                    *new_ptr.add(i),
692                    (i % 256) as u8,
693                    "Data within new size should be preserved"
694                );
695            }
696
697            xila_memory_deallocate(new_pointer);
698        }
699    }
700
701    /// Tests reallocation to zero size (equivalent to deallocation).
702    ///
703    /// When realloc is called with size 0, it should behave like free(),
704    /// deallocating the memory and potentially returning null.
705    #[test]
706    fn test_reallocate_to_zero() {
707        unsafe {
708            // Test reallocating to zero size (equivalent to deallocation)
709            let initial_size = 64;
710
711            let pointer = xila_memory_reallocate(core::ptr::null_mut(), initial_size);
712            assert!(!pointer.is_null(), "Initial allocation should succeed");
713
714            let new_pointer = xila_memory_reallocate(pointer, 0);
715            // Zero-size reallocation might return null or a valid pointer
716            // If it returns a valid pointer, we should deallocate it
717            if !new_pointer.is_null() {
718                xila_memory_deallocate(new_pointer);
719            }
720        }
721    }
722
723    /// Tests that the system page size is reasonable and valid.
724    ///
725    /// This test verifies that:
726    /// - Page size is greater than 0
727    /// - Page size is at least 4KB (common minimum)
728    /// - Page size is a power of 2 (hardware requirement)
729    #[test]
730    fn test_get_page_size() {
731        let page_size = xila_memory_get_page_size();
732
733        // Page size should be a power of 2 and at least 4KB on most systems
734        assert!(page_size > 0, "Page size should be greater than 0");
735        assert!(page_size >= 4096, "Page size should be at least 4KB");
736        assert!(
737            page_size.is_power_of_two(),
738            "Page size should be a power of 2"
739        );
740    }
741
742    /// Tests that data cache flushing doesn't cause crashes.
743    ///
744    /// This is a basic safety test to ensure the cache flush operation
745    /// completes without errors. The actual cache flush behavior is
746    /// hardware-dependent and difficult to test directly.
747    #[test]
748    fn test_flush_data_cache() {
749        // Test that flushing data cache doesn't crash
750        xila_memory_flush_data_cache();
751    }
752
753    /// Tests instruction cache flushing on executable memory.
754    ///
755    /// This test verifies that instruction cache flushing works correctly
756    /// on executable memory regions, which is essential for dynamic code
757    /// generation and just-in-time compilation.
758    #[test]
759    fn test_flush_instruction_cache() {
760        unsafe {
761            // Test flushing instruction cache with valid memory
762            let size = 128;
763            let alignment = 8;
764            let capabilities = XILA_MEMORY_CAPABILITIES_EXECUTE;
765            let hint_address = core::ptr::null_mut();
766
767            let pointer = xila_memory_allocate(hint_address, size, alignment, capabilities);
768            assert!(
769                !pointer.is_null(),
770                "Executable memory allocation should succeed"
771            );
772
773            // Flush instruction cache for this memory region
774            xila_memory_flush_instruction_cache(pointer, size);
775
776            xila_memory_deallocate(pointer);
777        }
778    }
779
780    #[test]
781    fn test_multiple_allocations() {
782        unsafe {
783            let mut pointers = Vec::new();
784            let allocation_count = 10;
785            let size = 64;
786            let alignment = 8;
787            let capabilities = 0;
788
789            // Allocate multiple memory blocks
790            for _ in 0..allocation_count {
791                let pointer =
792                    xila_memory_allocate(core::ptr::null_mut(), size, alignment, capabilities);
793                assert!(!pointer.is_null(), "Each allocation should succeed");
794                pointers.push(pointer);
795            }
796
797            // Verify each allocation is unique and writable
798            for (i, &pointer) in pointers.iter().enumerate() {
799                let ptr = pointer as *mut u8;
800                let test_value = (i % 256) as u8;
801                *ptr = test_value;
802                assert_eq!(
803                    *ptr, test_value,
804                    "Each allocation should be independently writable"
805                );
806            }
807
808            // Deallocate all memory blocks
809            for &pointer in &pointers {
810                xila_memory_deallocate(pointer);
811            }
812        }
813    }
814
815    #[test]
816    fn test_allocation_tracking() {
817        unsafe {
818            // Test that allocations are properly tracked for deallocation
819            let size = 128;
820            let alignment = 8;
821            let capabilities = 0;
822
823            let pointer =
824                xila_memory_allocate(core::ptr::null_mut(), size, alignment, capabilities);
825            assert!(!pointer.is_null(), "Allocation should succeed");
826
827            // The allocation should be tracked in the allocations table
828            // We can't directly access the table, but deallocation should work
829            xila_memory_deallocate(pointer);
830
831            // Double deallocation should be safe (should be handled gracefully)
832            xila_memory_deallocate(pointer);
833        }
834    }
835
836    #[test]
837    fn test_reallocation_tracking() {
838        unsafe {
839            // Test that reallocations properly update the tracking table
840            let initial_size = 64;
841            let new_size = 128;
842
843            let pointer = xila_memory_reallocate(core::ptr::null_mut(), initial_size);
844            assert!(!pointer.is_null(), "Initial reallocation should succeed");
845
846            let new_pointer = xila_memory_reallocate(pointer, new_size);
847            assert!(!new_pointer.is_null(), "Reallocation should succeed");
848
849            // The new pointer should be properly tracked
850            xila_memory_deallocate(new_pointer);
851        }
852    }
853
854    #[test]
855    fn test_double_free_handling() {
856        unsafe {
857            // Test that double-free is handled gracefully
858            let size = 64;
859            let alignment = 8;
860            let capabilities = 0;
861
862            let pointer =
863                xila_memory_allocate(core::ptr::null_mut(), size, alignment, capabilities);
864            assert!(!pointer.is_null(), "Allocation should succeed");
865
866            // First deallocation should succeed
867            xila_memory_deallocate(pointer);
868
869            // Second deallocation should be ignored (no crash)
870            xila_memory_deallocate(pointer);
871        }
872    }
873}