Standard library · System and tools

cpu

Use the processor's AES and carry-less multiply instructions, and ask what the processor offers.

import cpu

Modern processors can run one round of AES, or multiply two 64-bit values without carries, in a single instruction. This package makes those instructions available on u128 values, and tells you at run time whether the machine in front of you has them. tls uses it for AES-GCM, so the VPN and proxy protocols and the TLS record layer run on the fast path by themselves; most programs never need to call this package.

The instructions are real only when the program is built with the LLVM backend, which every release build uses. On the C backend, the native backend, the interpreter and in the browser every function still works, but as a slow software model of the instruction, and HasHardwareCrypto is false so that callers choose their own faster code. The software model gives the same bytes, which is what lets a test run the same data both ways.

Things to know:

  • Test first, then call. The Hw functions work everywhere, but only where HasHardwareCrypto is true are they one instruction each.
  • The switch is read when a cipher is made. SetHardware(false) affects the next AeadNew or one shot call, not a context that already exists. The environment variable SALAM_NO_HWCRYPTO does the same for a whole run.
  • HwClmul wants a literal. Its third argument becomes part of the instruction, so write 0, 1, 16 or 17 there, not a variable.
  • Constant time. The instructions take the same time for any data, which the table based software AES cannot promise.

Check the fast path against the slow one

SetHardware turns the fast path off, so one program can seal the same message both ways and compare. This is how the tests of the package check that the two paths give the same bytes. On a machine without the instructions both runs take the software path and the answer is still the same.

cpu-guide-switch.salam
import cpu
import mem
import tls

func hex(p: void*, n: int): str:
    b := p as u8*
    digits := "0123456789abcdef"
    mut s := ""
    repeat n in i:
        v := b[i] as int
        s += digits.substr(v >> 4, 1)
        s += digits.substr(v & 15, 1)
    end
    ret s
end

func seal(): str:
    key := mem.AllocateZeroed(16 as u64)
    nonce := mem.AllocateZeroed(12 as u64)
    msg := mem.AllocateZeroed(32 as u64)
    out := mem.AllocateZeroed(48 as u64)
    tls.AesGcmSealRaw(key, 16, nonce, null, 0, msg, 32, out)
    r := hex(out, 48)
    mem.Free(key)
    mem.Free(nonce)
    mem.Free(msg)
    mem.Free(out)
    ret r
end

func main:
    cpu.SetHardware(true)
    fast := seal()
    cpu.SetHardware(false)
    slow := seal()
    println "same bytes:", fast == slow
    println "tag:", fast.substr(64, 32)
end
Outputsame bytes: true tag: 40490af4805606b2a3a2e793e3500066

The tag line is the tag of 32 zero bytes under an all zero key and nonce; the algorithm fixes it, so it is the same on every machine.

Which function for which job

JobUse
Is the fast path available and on?HasHardwareCrypto
Which family of processor?HasAesNi (x86-64), HasArmCrypto (AArch64), or the bits of Features
Force the software pathSetHardware(false) or SALAM_NO_HWCRYPTO=1
One round of AES on a blockHwAesEnc and HwAesEncLast
A carry-less product, as in GHASHHwClmul
Move a block in and out of memoryHwLoad and HwStore
Reverse the bytes of a block, or swap its halvesHwBswap and HwSwap64

Constants and variables

FEATURE_HW_CRYPTO

const FEATURE_HW_CRYPTO: i32 = 1
The bit of Features that says the AES and carry-less multiply instructions are there and the program was built to use them.
cpu-Intrinsics.salam
import cpu
import mem

func show(label: str, x: u128):
    b := mem.AllocateZeroed(16 as u64)
    cpu.HwStore(b, x)
    p := b as u8*
    digits := "0123456789abcdef"
    mut s := ""
    repeat 16 in i:
        v := p[i] as int
        s += digits.substr(v >> 4, 1)
        s += digits.substr(v & 15, 1)
    end
    println label, s
    mem.Free(b)
end

func main:
    b := mem.AllocateZeroed(16 as u64)
    p := b as u8*
    repeat 16 in i:
        p[i] = (i * 17) as u8
    end
    x := cpu.HwLoad(b)
    show("load ", x)
    show("round", cpu.HwAesEnc(x, x))
    show("last ", cpu.HwAesEncLast(x, x))
    show("mul  ", cpu.HwClmul(x, x, 0))
    show("mulhi", cpu.HwClmul(x, x, 17))
    show("swap ", cpu.HwSwap64(x))
    show("bswap", cpu.HwBswap(x))
    cpu.SetHardware(false)
    println "off:", cpu.HasHardwareCrypto()
    cpu.SetHardware(true)
    f := cpu.Features()
    ok := (f & cpu.FEATURE_HW_CRYPTO) != 0
    println "on matches the processor:", cpu.HasHardwareCrypto() == ok
    println "families agree:", cpu.HasAesNi() == (ok and (f & cpu.FEATURE_X86) != 0), cpu.HasArmCrypto() == (ok and (f & cpu.FEATURE_ARM64) != 0), cpu.HasPclmul() == cpu.HasAesNi()
    mem.Free(b)
end
Outputload 00112233445566778899aabbccddeeff round 6368c4eab0329d01259f964f1e36645c last 63ed8e255fbb4eb44c58394e875fdd15 mul 00000101040405051010111114141515 mulhi 40404141444445455050515154545555 swap 8899aabbccddeeff0011223344556677 bswap ffeeddccbbaa99887766554433221100 off: false on matches the processor: true families agree: true true true

FEATURE_X86

const FEATURE_X86: i32 = 2
The bit of Features that says the processor is x86-64 (AES-NI and PCLMULQDQ).

The example for this item is shown with the ones above: cpu-Intrinsics.salam

FEATURE_ARM64

const FEATURE_ARM64: i32 = 4
The bit of Features that says the processor is AArch64 (the Crypto Extensions).

The example for this item is shown with the ones above: cpu-Intrinsics.salam

Functions

Features

func Features(): int
The features of this processor as bits: FEATURE_HW_CRYPTO together with FEATURE_X86 or FEATURE_ARM64. It is 0 when the backend does not lower the instructions or the processor lacks them.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HwAesEnc

func HwAesEnc(state: u128, key: u128): u128
One round of AES on a block, the aesenc of x86 (SubBytes, ShiftRows, MixColumns, then xor with the round key). The block is the 16 bytes in memory order, read as a little endian u128.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HwAesEncLast

func HwAesEncLast(state: u128, key: u128): u128
The last round of AES, without MixColumns, the aesenclast of x86.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HwClmul

func HwClmul(a: u128, b: u128, imm: int): u128
The carry-less product of one 64-bit half of a and one of b, as the 128-bit result. Bit 0 of imm picks the half of a and bit 4 the half of b, as in pclmulqdq. It must be an integer literal.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HwSwap64

func HwSwap64(x: u128): u128
Swaps the two 64-bit halves.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HwBswap

func HwBswap(x: u128): u128
Reverses all 16 bytes.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HwLoad

func HwLoad(p: void*): u128
Reads 16 bytes from any address, aligned or not.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HwStore

func HwStore(p: void*, x: u128)
Writes 16 bytes to any address, aligned or not.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

SetHardware

func SetHardware(enable: bool)
Turns the fast path on or off for ciphers made afterwards. It cannot turn on what the processor or the backend does not have.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HasHardwareCrypto

func HasHardwareCrypto(): bool
True when the instructions are there, the build uses them, and the switch is on.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HasAesNi

func HasAesNi(): bool
True on an x86-64 processor with AES-NI, PCLMULQDQ, SSSE3 and SSE4.1, when the build uses them.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HasPclmul

func HasPclmul(): bool
True when carry-less multiplication is available; it is checked together with AES, so it equals HasAesNi.

The example for this item is shown with the ones above: cpu-Intrinsics.salam

HasArmCrypto

func HasArmCrypto(): bool
True on an AArch64 processor with the AES and PMULL instructions, when the build uses them.

The example for this item is shown with the ones above: cpu-Intrinsics.salam