Memory

There is no garbage collector and no malloc. One fixed arena of 4 MiB hands out 16-byte aligned blocks, a free list per size class takes them back, and the compiler injects a reference count per scope: an object is born at new and handed back the moment the last name holding it stops. A loop that churns objects reuses one block forever.

Who owns what #

"Counted" means a class, an interface, a delegate or a T[]; a struct has none.

the holderowns
a local of counted typereleases at the end of its block, and on the way out of a break, continue or return that leaves it
a field, or an element of a T[]released when the holder dies, and when the slot is overwritten
a capture by value in a closurereleased with the closure
a global, a static field, a Singletonnever: it is a root
a parameter of class typeborrows — it carries no count, and reassigning it is refused

A value produced and handed straight to a call, f(new Cell(1)), has no owner: it is parked and released when the statement ends. A function returning a counted value hands the caller a reference it already owns. At zero the destructor runs first, then the counted fields are released, then the block goes back to its free list; a chain runs the derived class's destructor before the base's.

What is not reclaimed #

Declared debt, not silence — rt_live() counts all of it: a struct allocation (no vtable means no release function to reach) and every root: a global, a static field of class type, a Singleton.

rt_live() is the blocks handed out and not yet given back, rt_used() how far the bump pointer moved, rt_peak() its high-water mark; the fixtures use them as the oracle of every reclaim claim, and so does the program below.

Panics #

A guard writes teko: <cause> to standard error and exits 70: a null delegate called, an index into a null T[] or past its Length, a negative new T[n], an interface call on a class with no table, the arena exhausted. There is no recovery and no handler, and panic("...") is a function a program may call itself.

One program #

// expect-exit: 42
#include "rt.tk"

i64 dtors = 0;

class Cell {
    public i64 v;

    public Cell(i64 x) {
        v = x;
    }

    ~Cell() {
        dtors = dtors + 1;
    }
}

class Node {
    public Cell head;

    public Node(i64 x) {
        head = new Cell(x);
    }
}

i64 early(i64 want) {
    Cell outer = new Cell(1);
    {
        Cell inner = new Cell(2);
        if (inner.v == want) return outer.v;     // both released on the way out
    }
    return 0;
}

i64 main() {
    {
        Node n = new Node(5);
        if (rt_live() != 2) return 1;            // the node, and the cell it built
        n.head = new Cell(6);                    // the old cell has no owner left
        if (dtors != 1) return 2;
    }
    if (rt_live() != 0) return 3;
    if (dtors != 2) return 4;                    // the node released its field too

    dtors = 0;
    if (early(2) != 1) return 5;
    if (dtors != 2) return 6;

    i64 i = 0;
    loop {
        Cell c = new Cell(i);                    // born and released every iteration
        i = i + 1;
        if (i >= 100000) break;
    }
    if (rt_live() != 0) return 7;
    if (rt_peak() > 4096) return 8;              // one block, reused a hundred thousand times
    return 42;
}

An index a T[] cannot answer stops the program where it stands:

// expect-exit: 70
#include "rt.tk"

i64 main() {
    i64[] xs = new i64[3];
    return xs[5];                                // teko: index past the end of an array
}

The arena, the object layout and every message: memory.md, runtime.md.

Edit this page