---
title: Virtual Machine Memory Layout
description: Documentation for the Thru blockchain.
source_url:
  html: https://thru.org/docs/spec/vm/memory-layout/
  md: https://thru.org/docs/spec/vm/memory-layout.md
---

# Virtual Machine Memory Layout

## Address Space Overview

The Thru virtual machine uses a 48-bit segmented address space designed to provide memory isolation and efficient access to different types of data during program execution. The address format follows a three-component structure that enables both type safety and performance optimization.

## Address Format

Each virtual address is composed of three fields packed into a 48-bit value:

```plaintext
Bits: 47-40  |  39-24   |  23-0
     seg_type| seg_idx  | offset
    (8 bits) |(16 bits) |(24 bits)
```

> **Note:**
>
> For program code, the public C SDK exposes the address construction macro `TSDK_ADDR(seg_type, seg_idx, offset) = (seg_type << 40) | (seg_idx << 24) | offset`.

### Address Components

- **Segment Type** (8 bits): Determines the memory region type and access permissions
- **Segment Index** (16 bits): Identifies the specific segment within the type
- **Offset** (24 bits): Byte offset within the segment (up to 16MB per segment)

## Memory Segment Types

### 0x00: Read-Only Data Segments

Contains immutable data that programs can read but never modify.

| Segment Index | Name | Purpose | Notes |
| - | - | - | - |
| 0x0000 | NULL | Invalid/null segment | Always causes access violation |
| 0x0001 | TXN\_DATA | Transaction data | Contains serialized transaction |
| 0x0002 | SHADOW\_STACK | VM shadow stack | Call frame metadata (public portion) - see [Shadow Stack Frame Structure](#shadow-stack-frame-structure) |
| 0x0003 | PROGRAM | Program bytecode | Executable RISC-V code |
| 0x0004 | BLOCK\_CTX | Block context | Block metadata and timing info |

> **Caution:**
>
> Write operations to read-only segments cause access violations and will fail.

> **Caution:**
>
> Note that programs should not pass pointers to their own program bytecode to other programs, since the bytecode will be changed upon invocation to the invoked program’s bytecode.

## Block Context History

The block context segment exposes a rolling window of recent blocks, spaced evenly within the segment to allow direct pointer math from contracts.

| Concept | Value | Details |
| - | - | - |
| Addressing | `offset = blocks_ago * 0x1000` | `blocks_ago = 0` is the current block |
| Per-block stride | `0x1000` bytes (`TN_VM_BLOCK_CTX_SPACING`) | Each context is aligned by 4096 bytes |
| History window | 512 blocks (`TN_RUNTIME_CTX_BLOCK_SPAN`) | Accessing further back or before slot 0 faults |

Context structure (offsets relative to the start of a block’s window):

| Offset | Field | Type | Description |
| - | - | - | - |
| `0x00` | `slot` | `ulong` | Slot number of the block |
| `0x08` | `block_time` | `ulong` | Unix time in nanoseconds |
| `0x10` | `block_price` | `ulong` | Block price |
| `0x18` | `state_root` | `fd_hash_t` (32B) | State Merkle root |
| `0x38` | `cur_block_hash` | `fd_hash_t` (32B) | Block hash |
| `0x58` | `block_producer` | `fd_pubkey_t` (32B) | Producer public key |

Any access beyond this layout or outside the history window triggers an access violation.

### Shadow Stack Frame Structure

The shadow stack segment (0x0002) provides read-only access to call frame metadata, enabling cross-program communication and state inspection during execution. Each call frame stores information about the current program invocation context.

#### Frame Organization

The shadow stack maintains metadata for up to 17 frames:

- **Frame 0**: Reserved frame (frame -1 in implementation) containing all zeros
- **Frames 1-16**: Active call frames for program invocations (call depths 0-15)

Programs access frame data using the shadow stack segment with the frame index encoded in the offset.

#### Frame Structure

Each shadow stack frame contains the following fields:

| Offset | Field | Type | Size | Description |
| - | - | - | - | - |
| 0x00 | `program_acc_idx` | `ushort` | 2 bytes | Account index of the program for this frame |
| 0x02 | `stack_pages` | `ushort` | 2 bytes | Total size of stack region in pages (4KB pages) |
| 0x04 | `heap_pages` | `ushort` | `2 bytes` | Total size of heap region in pages (4KB pages) |
| 0x06 | `padding` | - | 2 bytes | Alignment padding |
| 0x08 | `saved_regs` | `ulong[32]` | 256 bytes | Saved register state (32 registers × 8 bytes each) |

**Total frame size**: 264 bytes per frame

#### Register State

The `saved_regs` array stores all 32 RISC-V registers (x0-x31) at the time of program invocation.

> **Note:**
>
> The register values stored in the public shadow stack represent the state at invoke time. When a program exits, the VM restores registers from this public shadow stack.

> **Tip:**
>
> **Compute Cost**: Each invocation saves 256 bytes (registers to public shadow stack) and restores 256 bytes (from public shadow stack), for a total of 512 bytes of memory operations. The base syscall cost is 512 CUs (32 registers × 8 bytes × 2 operations).

### 0x02: Account Metadata

Provides access to account metadata structures.

| Field | Description | Access Notes |
| - | - | - |
| seg\_idx | Account index in transaction | Must be < transaction account count |
| offset | Byte offset in metadata | Limited to `TSDK_ACCOUNT_META_FOOTPRINT` bytes in the public SDK view |

- **Size**: Exposed to programs as `TSDK_ACCOUNT_META_FOOTPRINT` bytes through `tsdk_account_meta_t`
- **Access**: Read-only from VM perspective
- **Non-existent accounts**: Returns pointer to zeroed metadata structure
- **Invalid access**: Access violations occur for out-of-bounds or invalid account indices

### 0x03: Account Data

Page-based access to account data with copy-on-write semantics.

**Address fields**

| Field | Description | Constraints |
| - | - | - |
| seg\_idx | Account index in transaction | Must be < transaction account count |
| offset | Byte offset into account data | Single memory access must not span across a 4KB page boundary. For example, reading 8 bytes at offset 4093 would fault as it spans across 4096. |

| Feature | Details |
| - | - |
| **Page Size** | 4096 bytes |
| **Alignment** | Enforced at page boundaries for cross-page access |
| **Write Access** | Requires account to be writable by current program |
| **COW Pages** | Automatic copy-on-write for modifications |

> **Tip:**
>
> Account data accesses that span page boundaries cause access violations. Design your data structures to respect 4KB page alignment.

### 0x04: Event Data

Reserved for event emission (implementation details in event subsystem).

### 0x05: Stack

Stack memory segment that grows downward from high addresses to low addresses.

| Characteristic | Value |
| - | - |
| **Growth Direction** | Downward (0xFFFFFF → 0x000000) |
| **Page Size** | 4096 bytes |
| **Max Segments** | 1 per VM instance |
| **Size Limit** | 16MB per segment |
| **Permission Model** | Frame-based access control |

**Growth Behavior:** The stack segment grows downward, meaning as more memory is allocated, the segment size increases toward lower addresses. When you allocate new stack space, the valid address range expands from the bottom (higher addresses) toward the top (lower addresses), similar to traditional hardware stacks.

**Address Layout:**

- Virtual address range: `0xFFFFFF` down to current stack size
- Stack pointer typically points to `0x000000` offset initially
- As stack grows, valid addresses extend from `0xFFFFFF` downward

### 0x07: Heap

Heap memory segment that grows upward from low addresses to high addresses.

| Characteristic | Value |
| - | - |
| **Growth Direction** | Upward (0x000000 → 0xFFFFFF) |
| **Page Size** | 4096 bytes |
| **Max Segments** | 1 per VM instance |
| **Size Limit** | 16MB per segment |
| **Permission Model** | Frame-based access control |

**Growth Behavior:** The heap segment grows upward, meaning as more memory is allocated, the segment size increases toward higher addresses. When you allocate new heap space, the valid address range expands from the base (0x000000) toward higher addresses, similar to traditional heap allocators.

## Address Translation Process

The VM performs the following steps for each memory access:

1. **Address Validation**

   Check if the upper 16 bits of the 64-bit address are zero (ensuring 48-bit address space).

2. **Segment Extraction**

   Extract segment type, index, and offset from the address using bit manipulation.

3. **Alignment Check**

   Verify that the offset is properly aligned for the requested access size.

4. **Permission Validation**

   Check write permissions based on segment type and current execution context.

5. **Translation**

   Convert virtual address to host address based on segment type-specific logic. If any validation fails, an access violation occurs.

## Access Control and Permissions

### Write Access Control

| Segment Type | Write Behavior | Access Violation Conditions |
| - | - | - |
| Read-Only Data | Always fails | Any write attempt causes violation |
| Account Metadata | Always fails | Runtime-managed, no direct writes allowed |
| Account Data | Conditional | Violation if program doesn’t own account |
| Anonymous Data | Conditional | Violation if frame permissions insufficient |

### Frame-Based Security

Anonymous memory segments use a frame-based permission system:

- Each page is tagged with the frame index (program invocation call depth) that allocated it
- Pages can only be freed by program invocations at the same call depth or higher
- Deeper called programs cannot free pages allocated by shallower called programs

## Memory Management

### Page Allocation

- **Page Size**: 4096 bytes (TN\_RUNTIME\_PAGE\_SZ)
- **Allocation**: Dynamic allocation from a global page pool
- **Tracking**: Inverted page table for efficient management
- **Limits**: Enforced at transaction level for resource control

### Copy-on-Write (COW)

Account data uses COW semantics:

1. **Initial State**: Points to read-only account data
2. **First Write**: Allocates writable page and copies original data
3. **Subsequent Writes**: Operate on the writable copy
4. **Commit**: Modified pages are written back during transaction commit

## Access Violations

Memory access can fail in several ways, resulting in access violations:

| Violation Type | Cause | Details |
| - | - | - |
| **Invalid Address** | Malformed address or out-of-bounds | Upper 16 bits non-zero, or address exceeds segment bounds |
| **Permission Denied** | Unauthorized write access | Attempting to write to read-only segments or unowned accounts |
| **Page Boundary Cross** | Access spans multiple pages | Single access cannot cross 4KB page boundaries |
| **Resource Exhaustion** | No free pages available | Anonymous memory allocation when page pool is exhausted |
| **Alignment Violation** | Misaligned access | Offset not aligned to required boundary for access size |
| **Invalid Segment** | Unknown segment type/index | Segment type not implemented or segment index out of range |

> **Caution:**
>
> **Access Violation Behavior**: When any of these violations occur the VM will fault and exit, reverting the transaction.

## Performance Considerations

- **Page Alignment**: Design data structures to fit within 4KB pages
- **Sequential Access**: Prefer sequential memory access patterns
- **Segment Locality**: Group related data within the same segment type
- **COW Overhead**: First write to account data incurs page allocation cost

## Example Address Calculations

**Stack Address:**

```c
// Stack segment at offset 0x1000
ulong stack_addr = TSDK_ADDR(TSDK_SEG_TYPE_STACK, 0x0000, 0x001000);
// Result: 0x050000001000
```

**Account Data:**

```c
// Account index 5, offset 0x800
ulong account_addr = TSDK_ADDR(TSDK_SEG_TYPE_ACCOUNT_DATA, 5, 0x800);
// Result: 0x030005000800
```

**Transaction Data:**

```c
// Transaction data at offset 0x40
ulong txn_addr = TSDK_ADDR(TSDK_SEG_TYPE_READONLY_DATA, TSDK_SEG_IDX_TXN_DATA, 0x40);
// Result: 0x000001000040
```
