Docs/Advanced

SIMD vectors

Vector types for data-parallel math.

On this page

A vector type such as u8x16, i32x4 or f64x2 holds several numbers ("lanes") in one value, and one operation works on all lanes at once. The compiler uses the CPU's SIMD instructions where it can and plain instructions where it can't.

Vector types#

The name is the lane type followed by the lane count: u8x16, i16x8, i32x4, i64x2, f32x4, f64x2, and wider forms such as u8x32, f32x8 and i64x8. A vector is 64 to 512 bits.

Operators work per lane:

  • +, - and * on every vector, / on float vectors
  • &, |, ^ and ~ on integer vectors, << and >> by a constant count
  • == and != compare whole vectors and give a bool

Make and read vectors#

Valk
fn main() {
    let values = Array[i32]{ 1, 2, 3, 4 }
    let a = $load[i32x4](values, 0)  // 4 elements from index 0
    let b = $splat[i32x4](10)        // every lane 10
    let c = a * b + a

    println($extract(c, 3))   // 44
    println($reduce_add(c))   // 110

    let changed = $insert(a, 0, 9)
    println($extract(changed, 0)) // 9
}

$load[T](view, offset) reads from an Array, String, &[T] or other view of the lane type, starting at element offset; $store(view, offset, v) writes. Both are bounds checked and need no alignment.

Compare and select#

A compare gives a mask: an integer vector with all bits set in the lanes where it holds.

Valk
fn main() {
    let a = $load[i32x4](Array[i32]{ 1, 2, 3, 4 }, 0)
    let limit = $splat[i32x4](2)

    let big = $gt(a, limit)
    println($mask_bits(big))   // 12 (lanes 2 and 3)

    let capped = $select(big, limit, a)
    println($reduce_max(capped)) // 2
}

Example: count bytes#

Valk
fn count_spaces(text: local &[u8]) uint {
    let spaces = $splat[u8x16](' ')
    let total: uint = 0
    let i: uint = 0
    while i + 16 <= text.length {
        let chunk = $load[u8x16](text, i)
        total += $count_ones($mask_bits($eq(chunk, spaces)))
        i += 16
    }
    while i < text.length {
        if text[i] == ' ' : total++
        i++
    }
    return total
}

fn main() {
    println(count_spaces("a b c d e f g h i j k l m n o p q r")) // 17
}

Builtins#

Builtin Does
$splat[T](x) Every lane x
$load[T](view, offset), $store(view, offset, v) Read or write a vector in a view
$extract(v, i), $insert(v, i, x) Read or replace one lane
$shuffle(a, b, i0, i1, ...) Pick lanes by constant index: 0.. from a, then from b
$eq $ne $lt $gt $lte $gte(a, b) Per-lane compare, giving a mask
$select(mask, a, b) a where the mask is set, else b
$mask_bits(mask) One bit per lane, as a uint
$reduce_add, $reduce_min, $reduce_max Combine all lanes into one value
$reduce_and, $reduce_or, $reduce_xor Same, for integer vectors
$bitcast[T](v) Reinterpret as another vector type of the same size
$lookup(table, indexes) Byte table lookup, 16 or 32 lanes; needs $target_ssse3 / $target_avx2 on x86-64
$clmul(a, b) Carry-less multiply of two u64 into a u64x2; x86-64 with $target_pclmul

Where the type cannot be inferred, it goes in [...].

CPU features#

By default the build targets a baseline CPU. --cpu allows newer instructions for the whole program:

Terminal
valk build src -o app --release --cpu native     # this machine's CPU
valk build src -o app --release --cpu x86-64-v3  # a named CPU level

To ship one executable that still runs everywhere, give a single function a $target_* flag and call it only after core.cpu_has confirms the feature:

Valk
use valk.core

fn sum_avx2(values: local &[f32]) f32 $target_avx2 {
    let acc = $splat[f32x8](0)
    let i: uint = 0
    while i + 8 <= values.length {
        acc = acc + $load[f32x8](values, i)
        i += 8
    }
    let total = $reduce_add(acc)
    while i < values.length {
        total += values[i]
        i++
    }
    return total
}

fn sum_plain(values: local &[f32]) f32 {
    let total: f32 = 0
    each values as v : total += v
    return total
}

fn sum(values: local &[f32]) f32 {
    if core.cpu_has(.avx2) : return sum_avx2(values)
    return sum_plain(values)
}

fn main() {
    let values = Array[f32]{ 1.5 x 20 }
    println(sum(values)) // 30
}

The flags are $target_ssse3, $target_sse41, $target_avx, $target_avx2 and $target_pclmul (x86-64). Such a function cannot be $inline and cannot take or return a vector wider than 128 bits.