Module

std::memory

Memory Module

Low-level memory allocation and manipulation utilities.

Overview

This module provides the foundational memory operations for Ignis programs:

  • Allocation: Obtain raw memory from the runtime
  • Deallocation: Return memory to the runtime
  • Copying: Transfer data between memory regions
  • Moving: Transfer data with support for overlapping regions

Memory Model

  IGNIS MEMORY ARCHITECTURE
  ════════════════════════════════════════════════════════════════════════

  ┌─────────────────────────────────────────────────────────────────────┐
  │                         User Code                                   │
  │                                                                     │
  │   let ptr: *mut u64 = Memory::allocateVector<u64>(100);             │
  │   // ... use ptr ...                                                │
  │   Memory::free(ptr);                                                │
  └──────────────────────────────────┬──────────────────────────────────┘
                                     │
                                     ▼
  ┌─────────────────────────────────────────────────────────────────────┐
  │                    Memory Namespace (this module)                   │
  │                                                                     │
  │   allocate<T>       allocateVector<T>      allocateZeroed<T>        │
  │   reallocate<T>     reallocateVector<T>    allocateZeroedVector<T>  │
  │   free<T>           copy<T>                move<T>                  │
  │   copyBytes         moveBytes                                       │
  └──────────────────────────────────┬──────────────────────────────────┘
                                     │
                                     ▼
  ┌─────────────────────────────────────────────────────────────────────┐
  │                      Ignis Runtime (ignis_rt)                       │
  │                                                                     │
  │   ignis_alloc    ignis_free      ignis_realloc    ignis_calloc      │
  │   ignis_memcpy   ignis_memmove                                      │
  └──────────────────────────────────┬──────────────────────────────────┘
                                     │
                                     ▼
  ┌─────────────────────────────────────────────────────────────────────┐
  │                         C Runtime / OS                              │
  │                                                                     │
  │   malloc       free        realloc        calloc                    │
  │   memcpy       memmove                                              │
  └─────────────────────────────────────────────────────────────────────┘

Allocation Functions

  ALLOCATION METHODS COMPARISON
  ════════════════════════════════════════════════════════════════════════

  ┌─────────────────────┬─────────────────────────────────────────────────┐
  │ Function            │ Use Case                                        │
  ├─────────────────────┼─────────────────────────────────────────────────┤
  │ allocate<T>(size)   │ Raw bytes, manual size calculation              │
  │ allocateVector<T>(n)│ Array of n elements of type T                   │
  │ allocateZeroed<T>   │ Zero-initialized memory (manual elem size)      │
  │ allocateZeroedVec   │ Zero-initialized array of type T                │
  └─────────────────────┴─────────────────────────────────────────────────┘

  Example: Allocating 10 u64 values

  Method 1: allocateVector (recommended)
  ┌─────────────────────────────────────────────────────────────────────┐
  │  let ptr: *mut u64 = Memory::allocateVector<u64>(10);               │
  │                                                                     │
  │  Internally: ignis_alloc(10 * @sizeOf<u64>()) = ignis_alloc(80)      │
  └─────────────────────────────────────────────────────────────────────┘

  Method 2: allocate (manual size)
  ┌─────────────────────────────────────────────────────────────────────┐
  │  let ptr: *mut u64 = Memory::allocate<u64>(80);                     │
  │                                                                     │
  │  Directly: ignis_alloc(80)                                          │
  └─────────────────────────────────────────────────────────────────────┘

Copy vs Move

  COPY: Source and destination must NOT overlap
  ════════════════════════════════════════════════════════════════════════

  Valid copy (no overlap):
  ┌────────────────────────────────────────────────────────────────────┐
  │   Source:      ┌─────────────────┐                                 │
  │                │  A  B  C  D  E  │                                 │
  │                └─────────────────┘                                 │
  │                                                                    │
  │   Destination:                   ┌─────────────────┐               │
  │                                  │  ?  ?  ?  ?  ?  │               │
  │                                  └─────────────────┘               │
  │                                           ↓                        │
  │   After copy:                    ┌─────────────────┐               │
  │                                  │  A  B  C  D  E  │  ✓            │
  │                                  └─────────────────┘               │
  └────────────────────────────────────────────────────────────────────┘

  INVALID copy (overlap) - use move instead:
  ┌────────────────────────────────────────────────────────────────────┐
  │   Source:      ┌─────────────────┐                                 │
  │                │  A  B  C  D  E  │                                 │
  │                └─────────────────┘                                 │
  │                      ▲                                             │
  │                      └──── Overlap! ────┐                          │
  │                                         ▼                          │
  │   Destination:       ┌─────────────────┐                           │
  │                      │  ?  ?  ?  ?  ?  │                           │
  │                      └─────────────────┘                           │
  │                                                                    │
  │   ⚠ UNDEFINED BEHAVIOR with copy - source may be corrupted!        │
  └────────────────────────────────────────────────────────────────────┘

  MOVE: Handles overlapping regions safely
  ════════════════════════════════════════════════════════════════════════

  Overlapping move (forward):
  ┌────────────────────────────────────────────────────────────────────┐
  │   Before:   │  A  B  C  D  E  ?  ?  │                              │
  │             └─────────────────┘                                    │
  │                  └─── move 2 positions right ───┘                  │
  │                                                                    │
  │   After:    │  A  B  A  B  C  D  E  │                              │
  │                      └─────────────────┘  ✓                        │
  │                                                                    │
  │   Move copies from END to avoid overwriting source data            │
  └────────────────────────────────────────────────────────────────────┘

Safety

All functions in this module are unsafe in the sense that:

  • Pointers must be valid and properly aligned
  • Sizes must not exceed allocated regions
  • Memory must not be freed twice
  • Memory must not be used after being freed

The caller is responsible for ensuring these invariants.

Example Usage

import Memory from "std/memory";

// Allocate an array of 100 integers
let data: *mut i32 = Memory::allocateVector<i32>(100);

// Initialize elements
for (let i: u64 = 0; i < 100; i = i + 1) {
  *(data + i) = i as i32;
}

// Resize to 200 elements
data = Memory::reallocateVector<i32>(data, 200);

// Copy first 50 elements to another buffer
let backup: *mut i32 = Memory::allocateVector<i32>(50);
Memory::copy<i32>(backup, data, 50);

// Clean up
Memory::free(backup);
Memory::free(data);
namespace __memory

External declarations for the Ignis runtime memory functions.

These functions map directly to the C runtime allocator and are the lowest-level memory operations available in Ignis.

Safety

All functions require:

  • Valid pointers (for functions that take pointers)
  • Correct size calculations
  • Proper alignment (handled by the runtime)

Functions

function ignis_alloc(size: u64): *mut u8

Allocates size bytes using the runtime allocator.

function ignis_alloc_aligned(size: u64, alignment: u64): *mut u8

Allocates size bytes with an explicit alignment.

function ignis_calloc(count: u64, size: u64): *mut u8

Allocates zeroed storage for count * size bytes.

function ignis_calloc_aligned(count: u64, size: u64, alignment: u64): *mut u8

Allocates zeroed storage with explicit alignment.

function ignis_free(ptr: *mut u8): void

Frees a pointer previously returned by the runtime allocator.

function ignis_memcpy(dest: *mut u8, src: *u8, n: u64): void

Copies n bytes from src to dest; regions must not overlap.

function ignis_memmove(dest: *mut u8, src: *u8, n: u64): void

Moves n bytes from src to dest; regions may overlap.

function ignis_realloc(ptr: *mut u8, size: u64): *mut u8

Reallocates ptr to size bytes using default alignment.

function ignis_realloc_aligned(ptr: *mut u8, size: u64, alignment: u64): *mut u8

Reallocates ptr to size bytes with an explicit alignment.

namespace GlobalAllocator

Layout-aware allocator contract used by the collections foundation.

Non-zero layouts are infallible from Ignis code: allocation failure traps in the runtime instead of returning Option, Result, or null. Zero-sized layouts return null because no backing storage is needed.

Functions

function allocate(layout: Layout): *mut u8

Allocates storage for layout, returning null for zero-sized layouts.

function allocateZeroed(layout: Layout): *mut u8

Allocates zero-initialized storage for layout.

function free(pointer: *mut u8): void

Frees a pointer allocated by GlobalAllocator or the runtime allocator.

function reallocate(pointer: *mut u8, layout: Layout): *mut u8

Resizes an existing allocation to layout.

Passing a zero-sized layout frees the existing pointer and returns null.

namespace Memory

Memory allocation and manipulation namespace.

Provides type-safe wrappers around the raw memory allocation functions. All allocation functions return typed pointers, and generic functions automatically calculate sizes based on the type parameter.

Thread Safety

The underlying allocator is typically thread-safe (depends on the C runtime), but concurrent access to the same memory region requires external synchronization.

Functions

function allocate<T>(size: u64): *mut T

Allocates size bytes of uninitialized memory.

This is the most basic allocation function. The caller specifies the exact number of bytes needed.

Type Parameters

  • T - The type to cast the returned pointer to

Arguments

  • size - The number of bytes to allocate

Returns

A pointer to the allocated memory, cast to *mut T. Allocation failure aborts in the runtime; only size 0 may return null.

Memory State

  allocate<u64>(24)
  ════════════════════════════════════════════════════════════════════════

  ┌─────────────────────────────────────────────────────────────────────┐
  │   Returned pointer                                                  │
  │         │                                                           │
  │         ▼                                                           │
  │        ┌────────────────────────────────────────────┐               │
  │        │  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?  ...  │                │
  │        │         (24 bytes, UNINITIALIZED)          │               │
  │        └────────────────────────────────────────────┘               │
  │                                                                     │
  │   ⚠ Contents are undefined - must initialize before reading!        │
  └─────────────────────────────────────────────────────────────────────┘

Example

// Allocate 1024 bytes for a buffer
let buffer: *mut u8 = Memory::allocate<u8>(1024);

// Allocate space for 3 u64 values manually
let values: *mut u64 = Memory::allocate<u64>(24);  // 3 * 8 bytes
function allocateVector<T>(count: u64): *mut T

Allocates memory for count elements of type T.

Automatically calculates the required size using @sizeOf<T>(). This is the recommended way to allocate arrays.

Type Parameters

  • T - The element type

Arguments

  • count - The number of elements to allocate space for

Returns

A pointer to the allocated memory, typed as *mut T. Allocation failure aborts in the runtime; only count 0 may return null.

Size Calculation

  allocateVector<u64>(10)
  ════════════════════════════════════════════════════════════════════════

  total_size = count * @sizeOf<T>()
             = 10 * 8
             = 80 bytes

  ┌─────────────────────────────────────────────────────────────────┐
  │  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐  │
  │  │  0  │  1  │  2  │  3  │  4  │  5  │  6  │  7  │  8  │  9  │  │
  │  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │  │
  │  └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘  │
  │  ◄──────────────────── 80 bytes ─────────────────────────────►  │
  └─────────────────────────────────────────────────────────────────┘

Example

// Allocate array of 100 i32 values
let numbers: *mut i32 = Memory::allocateVector<i32>(100);

// Allocate array of 50 custom structs
record Point { x: f64; y: f64; }
let points: *mut Point = Memory::allocateVector<Point>(50);
function allocateZeroed<T>(count: u64, elementSizeBytes: u64): *mut T

Allocates zero-initialized memory for count elements.

All bytes in the allocated region are set to zero.

Type Parameters

  • T - The type to cast the returned pointer to

Arguments

  • count - The number of elements
  • elementSizeBytes - The size of each element in bytes

Returns

A pointer to zero-initialized memory, cast to *mut T. Allocation failure aborts in the runtime; only count 0 may return null.

Memory State

  allocateZeroed<u64>(10, 8)
  ════════════════════════════════════════════════════════════════════════

  ┌───────────────────────────────────────────────────────────────────┐
  │  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐    │
  │  │  0  │  0  │  0  │  0  │  0  │  0  │  0  │  0  │  0  │  0  │    │
  │  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │ 8B  │    │
  │  └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘    │
  │                                                                   │
  │  All bytes are guaranteed to be zero                              │
  └───────────────────────────────────────────────────────────────────┘

Example

// Allocate 100 zero-initialized u64 values
let zeros: *mut u64 = Memory::allocateZeroed<u64>(100, 8);

// First element is guaranteed to be 0
assert(*zeros == 0);
function allocateZeroedVector<T>(count: u64): *mut T

Allocates zero-initialized memory for count elements of type T.

Combines the convenience of allocateVector with zero-initialization. This is the recommended way to allocate zero-initialized arrays.

Type Parameters

  • T - The element type

Arguments

  • count - The number of elements to allocate

Returns

A pointer to zero-initialized memory, typed as *mut T. Allocation failure aborts in the runtime; only count 0 may return null.

Example

// Allocate 100 zero-initialized counters
let counters: *mut u64 = Memory::allocateZeroedVector<u64>(100);

// All counters start at 0
for (let i: u64 = 0; i < 100; i = i + 1) {
  assert(*(counters + i) == 0);
}
function copy<T>(destination: *mut T, source: *T, count: u64): void

Copies count elements of type T from source to destination.

Automatically calculates the byte size using @sizeOf<T>().

Type Parameters

  • T - The element type

Arguments

  • destination - The target array
  • source - The source array
  • count - The number of elements to copy

Safety

  • Source and destination must NOT overlap
  • Both arrays must have at least count elements
  • If arrays may overlap, use move<T> instead

Example

let src: *mut i32 = Memory::allocateVector<i32>(100);
let dst: *mut i32 = Memory::allocateVector<i32>(100);

// Initialize source
for (let i: u64 = 0; i < 100; i = i + 1) {
  *(src + i) = i as i32;
}

// Copy all 100 elements
Memory::copy<i32>(dst, src as *i32, 100);
function copyBytes(destination: *mut u8, source: *u8, sizeBytes: u64): void

Copies sizeBytes bytes from source to destination.

This is a low-level byte copy. For typed copies, use copy<T>.

Arguments

  • destination - The target memory address
  • source - The source memory address
  • sizeBytes - The number of bytes to copy

Safety

  • Source and destination must NOT overlap
  • Both regions must be valid for the specified size
  • If regions may overlap, use moveBytes instead

Example

let src: *mut u8 = Memory::allocate<u8>(100);
let dst: *mut u8 = Memory::allocate<u8>(100);

// Copy 100 bytes
Memory::copyBytes(dst, src as *u8, 100);
function free<T>(pointer: *mut T): void

Frees a previously allocated memory block.

After calling free, the pointer is invalid and must not be used.

Type Parameters

  • T - The type of the pointer (for type safety)

Arguments

  • pointer - A pointer previously returned by an allocation function

Safety

  • Must not free the same pointer twice (double-free)
  • Must not use the pointer after freeing (use-after-free)
  • Must only free pointers returned by allocation functions

Example

let data: *mut u64 = Memory::allocateVector<u64>(100);
// ... use data ...

Memory::free(data);
// data is now invalid - do not use!
function move<T>(destination: *mut T, source: *T, count: u64): void

Moves count elements of type T from source to destination.

Handles overlapping regions correctly. Use this when source and destination arrays may overlap.

Type Parameters

  • T - The element type

Arguments

  • destination - The target array
  • source - The source array
  • count - The number of elements to move

Safety

  • Both arrays must have at least count elements
  • Safe to use even if arrays overlap

Example

let array: *mut i32 = Memory::allocateVector<i32>(100);

// Remove first element by shifting left
Memory::move<i32>(array, (array + 1) as *i32, 99);
function moveBytes(destination: *mut u8, source: *u8, sizeBytes: u64): void

Moves sizeBytes bytes from source to destination.

Unlike copyBytes, this function correctly handles overlapping regions.

Arguments

  • destination - The target memory address
  • source - The source memory address
  • sizeBytes - The number of bytes to move

Safety

  • Both regions must be valid for the specified size
  • Safe to use even if regions overlap

When to Use

  Use moveBytes when source and destination may overlap:

  ┌─────────────────────────────────────────────────────────────────────┐
  │   Shifting elements left in an array:                               │
  │                                                                     │
  │   Before: │  A  B  C  D  E  F  G  │                                 │
  │                └────── move ──────┘                                 │
  │           └────── to here                                           │
  │                                                                     │
  │   After:  │  C  D  E  F  G  F  G  │                                 │
  └─────────────────────────────────────────────────────────────────────┘

Example

let buffer: *mut u8 = Memory::allocate<u8>(100);

// Shift contents 10 bytes earlier (overlapping)
Memory::moveBytes(buffer, (buffer + 10) as *u8, 90);
function reallocate<T>(pointer: *mut u8, size: u64): *mut T

Resizes a previously allocated memory block.

The contents of the memory block are preserved up to the minimum of the old and new sizes. If the new size is larger, the additional memory is uninitialized.

Type Parameters

  • T - The type to cast the returned pointer to

Arguments

  • pointer - A pointer previously returned by an allocation function
  • size - The new size in bytes

Returns

A pointer to the resized memory block. May be the same as pointer or a new location. Reallocation failure aborts in the runtime.

Reallocation Behavior

  reallocate(ptr, newSize)
  ════════════════════════════════════════════════════════════════════════

  Case 1: Shrinking (newSize < oldSize)
  ┌─────────────────────────────────────────────────────────────────────┐
  │   Before:  ┌─────────────────────────────────────┐                  │
  │            │  A  B  C  D  E  F  G  H  I  J       │                  │
  │            └─────────────────────────────────────┘                  │
  │                                                                     │
  │   After:   ┌───────────────────┐                                    │
  │            │  A  B  C  D  E    │  (data preserved)                  │
  │            └───────────────────┘                                    │
  └─────────────────────────────────────────────────────────────────────┘

  Case 2: Growing (newSize > oldSize)
  ┌─────────────────────────────────────────────────────────────────────┐
  │   Before:  ┌───────────────────┐                                    │
  │            │  A  B  C  D  E    │                                    │
  │            └───────────────────┘                                    │
  │                                                                     │
  │   After:   ┌─────────────────────────────────────┐                  │
  │            │  A  B  C  D  E  ?  ?  ?  ?  ?      │                   │
  │            └─────────────────────────────────────┘                  │
  │                               │                                     │
  │                               └── New space is UNINITIALIZED        │
  └─────────────────────────────────────────────────────────────────────┘

Warning

After calling reallocate, the original pointer may be invalid. Always use the returned pointer.

Example

let buffer: *mut u8 = Memory::allocate<u8>(100);
// ... fill buffer ...

// Grow buffer to 200 bytes
buffer = Memory::reallocate<u8>(buffer as *mut u8, 200);
function reallocateVector<T>(pointer: *mut T, count: u64): *mut T

Resizes an array to hold count elements of type T.

Automatically calculates the new size using @sizeOf<T>(). This is the recommended way to resize typed arrays.

Type Parameters

  • T - The element type

Arguments

  • pointer - A pointer previously returned by an allocation function
  • count - The new number of elements

Returns

A pointer to the resized array. May be the same location or moved. Reallocation failure aborts in the runtime; count 0 returns null.

Example

// Start with 10 elements
let data: *mut i32 = Memory::allocateVector<i32>(10);

// Grow to 100 elements
data = Memory::reallocateVector<i32>(data, 100);

// Original 10 elements are preserved
// Elements 10-99 are uninitialized