Cinder by Example

Cinder is small programming language for low-level development.

Pointers

Raw pointers let you read and write memory through an address. Dereference,

arithmetic, indexing, and pointer casts are always available.

Address-of

Taking the address of a local produces a pointer to it.

use "std/io.cnd";

fn main() -> i32 {
    let mut x = 5;
    let p = &x;
    *p += 10;

    print(&x, .I32);
    putchar(10);
    return 0;
}
$ ./pointers
15

Passing pointers to functions

The usual "method" pattern: pass &value, mutate through the pointer.

use "std/io.cnd";

fn swap(a: *i32, b: *i32) {
    let t = *a;
    *a = *b;
    *b = t;
}

fn main() -> i32 {
    let mut x = 1;
    let mut y = 2;
    swap(&x, &y);

    print(&x, .I32);

    let sp: []u8 = " ";
    print(&sp, .S);

    print(&y, .I32);
    putchar(10);

    return 0;
}
$ ./swap
2 1

Pointer to a slice byte

A string is a slice; s.ptr points at its first byte.

use "std/io.cnd";

fn main() -> i32 {
    let s = "hello";
    let p = s.ptr;
    let first = *p;

    let v: i32 = first as i32;
    print(&v, .I32);
    putchar(10);
    return 0;
}
$ ./strptr
104

null

The literal null is the null pointer. Never dereference it.

use "std/io.cnd";

fn main() -> i32 {
    let p: *i32 = null;
    if p == null {
        println("null pointer");
    }
    return 0;
}
$ ./null
null pointer

Pointer qualifiers

const reads only; volatile reads/writes go around optimization.

let a: *const u32 = &value;      // read-only
let b: *volatile u32 = ®     // volatile access
let c: *const volatile u32 = ®

*void

void is a pointer to an unknown type. Any pointer coerces to void

and may be cast back to a concrete pointer type.

use "std/io.cnd";

fn main() -> i32 {
    let mut n: u64 = 42;
    let opaque: *void = &n;
    let back: *u64 = opaque as *u64;

    let v = *back;
    print(&v, .U64);
    putchar(10);
    return 0;
}
$ ./voidptr
42

Pointer arithmetic

Arithmetic on pointers (and indexing) steps through the pointee.

use "std/io.cnd";

fn main() -> i32 {
    let arr = [10, 20, 30];
    let base = arr[..].ptr;   // convert the array to a slice, take its ptr
    let v1 = *base;
    print(&v1, .I32);

    let sp: []u8 = " ";
    print(&sp, .S);

    let v2 = *(base + 1);
    print(&v2, .I32);
    putchar(10);
    return 0;
}
$ ./ptradd
10 20

asm

Inline assembly is always available.

// docs: kernel
asm("wfi");

Operands

Outputs come after the first :; inputs after the second.

Constraints are quoted strings; = marks a write-only output.

The output must be a mutable variable, pointer dereference, or index expression.

let mut out: u64 = 0;
asm("movq $1, $0" : "=r"(out) : "r"(42u64));

let mut a: i32 = 10;
let mut b: i32 = 20;
let mut sum: i32 = 0;
asm("addl $1, $2, $0" : "=r"(sum) : "r"(a), "r"(b));

The result of the expression is the output value.

See Bare Metal for how this is used in a kernel.