Forge provides low-level memory control: stack locals, raw pointers, manual heap allocation, and an optional garbage collector on x86_64.
let x = 42
let p: ptr[int32] = &x # Address of a local
let q = 0x1000 as ptr[int32] # Integer literal as pointer (MMIO, etc.)
let addr = p as int64 # Pointer to integer
&local is implicitly coerced to ptr[T] when passed to a function:
def value_at(p: ptr[int32]) -> int32:
unsafe:
return *p
var n = 7
let v = value_at(&n) # v == 7
String literals have type ptr[char] (null-terminated, stored in .rodata).
Pointer dereference and pointer arithmetic are only allowed inside
unsafe blocks:
unsafe:
let v = *p # Read through pointer
*p = 42 # Write through pointer
p = p + 1 # Pointer arithmetic (byte offset)
There is no bounds checking — the caller is responsible for validity.
&x-style access through plain deref is effectively volatile (the compiler
emits real memory accesses), but for explicit hardware access the std.volatile
module provides width-correct loads and stores:
from std.volatile import load_u32, store_u32
var reg: uint32 = 0
store_u32(®, 0x1234)
let v = load_u32(®)
let x = 42 # Stack local
var y = 10
let arr = [1, 2, 3] # Stack array
All locals are 64-bit stack slots regardless of declared type.
std.alloc provides alloc/free over a compiler-emitted heap:
from std.alloc import alloc, free
let buf: ptr[char] = alloc(64)
unsafe:
*buf = (65 as char)
*(buf + 1) = (0 as char)
free(buf)
free returns blocks to the list for reuse. There is no garbage collector.The GC heap backs std.alloc. std.gc exposes collection and statistics:
from std.gc import collect, leak_check, heap_live
collect() # Force a mark-and-sweep pass
let live = heap_live() # Bytes currently live
let leaks = leak_check() # Bytes dropped without free
The collector is conservative: it scans the stack [rbp, stack_top] and
.rodata for words that look like heap pointers. Every function frame is
zeroed on return so dead frames cannot act as GC roots.
let size = sizeof(Point) # Struct size (fields, no padding)
let off = offsetof(Point, y) # Byte offset of field y
Fields are laid out sequentially without padding:
struct Point:
x: int32 # offset 0
y: int32 # offset 4
# sizeof(Point) == 8
from std.mem import copy_bytes, set_bytes, zero_bytes, compare_bytes
copy_bytes(dst, src, 100) # memcpy (handles overlap)
set_bytes(ptr, 0, 100) # memset
zero_bytes(ptr, 64) # secure zero
compare_bytes(a, b, 100) == 0 # memcmp
All take ptr[uint8] arguments and a uint64 byte count.
| Feature | x86_64 | x86_32 | x86_16 |
|---|---|---|---|
| Pointer size | 8 bytes | 4 bytes | 2 bytes (16-bit) |
Heap (std.alloc) |
First-fit + GC (4 MiB) | First-fit, no GC (4 MiB) | None |
GC (std.gc) |
Yes | No (clean error) | No |
| Floats | Yes | No | No |
ref[T]/refmut[T]/own[T] — only partially parsed; refmut and own
cannot be constructedslice[T]), fn types