std.cpu.dispatch — SIMD-Dispatch und f32-Arrays

Wählt zur Laufzeit den besten Rechen-Pfad (AVX2 → SSE2 → Scalar) und stellt float32-Arrays bereit, die 16-Byte-ausgerichtet im Speicher liegen. Der Dispatch basiert auf std.cpu.features.

Standard Library · std.cpu.features · Lyx-Dokumentation


Implementierungsstand: Diese Unit ist Teil von WP-GPU-02 des internen SIMD/GPU-Fahrplans. SSE2- und AVX2-Instruktionen werden vom Compiler-Codegen (CGN_BINOP SIMD) emittiert — die Stdlib-Funktionen hier sind das Dispatch-Gerüst und die Array-Verwaltung. Der Scalar-Fallback ist derzeit ein Placeholder (siehe Einschränkungen).

Import

import std.cpu.dispatch;

Zieht std.cpu.features automatisch mit.


Dispatch-Level

Konstante Wert Bedeutung
CPU_DISPATCH_SCALAR 0 Kein SIMD — reiner Scalar-Pfad
CPU_DISPATCH_SSE2 1 SSE2 verfügbar (128-Bit XMM-Register)
CPU_DISPATCH_AVX2 2 AVX2 verfügbar (256-Bit YMM-Register)

fn CpuDispatchLevel(): int64

Gibt das höchste verfügbare Level zurück (AVX2 > SSE2 > Scalar). Das Ergebnis wird gecacht; /proc/cpuinfo wird nur beim ersten Aufruf gelesen.


SIMD-Array-Layout

SimdAlloc erzeugt ein f32-Array im Speicher mit folgendem Layout:

Adresse   Inhalt
ptr-8     Elementanzahl (int64, 8 Byte)
ptr+0     Element 0 (f32, 4 Byte, 16-Byte-Grenze)
ptr+4     Element 1 (f32, 4 Byte)
ptr+8     Element 2 (f32, 4 Byte)
...

  • Basis-Pointer wird per mmap (anonym) angefordert: total = n×4 + 24 Byte.
  • 16-Byte-Ausrichtung: ptr = (base + 23) & ~15.
  • Elementanzahl steht bei ptr-8 (SimdLen liest dort).
  • f32-Werte werden als rohe 32-Bit-Bitmuster (int64) übergeben — kein automatischer Cast.

Funktionen

SimdAlloc

fn SimdAlloc(n: int64): int64

Allokiert ein 16-Byte-ausgerichtetes f32-Array für n Elemente (via mmap). Gibt den ausgerichteten Pointer zurück, oder 0 bei Fehler. Elementanzahl wird bei ptr-8 gespeichert.

Kein SimdFree — SIMD-Arrays müssen mit MmapFree(ptr, n*4+24) manuell freigegeben werden. Alternativ werden sie beim Prozessende vom Kernel zurückgefordert.

SimdAdd / SimdSub / SimdMul / SimdDiv

fn SimdAdd(src1: int64, src2: int64): int64   // result = src1 + src2
fn SimdSub(src1: int64, src2: int64): int64   // result = src1 - src2
fn SimdMul(src1: int64, src2: int64): int64   // result = src1 * src2
fn SimdDiv(src1: int64, src2: int64): int64   // result = src1 / src2

Elementweise f32-Arithmetik. Jede Funktion:

  1. liest n aus src1-8
  2. allokiert ein neues Array (SimdAlloc(n))
  3. rechnet und schreibt in das neue Array
  4. gibt den Pointer auf das neue Array zurück

Der Aufrufer ist für die Freigabe aller Arrays (src1, src2, Ergebnis) verantwortlich.

SimdGet / SimdSet

fn SimdGet(arr: int64, i: int64): int64     // liest Element i als f32-Bits
fn SimdSet(arr: int64, i: int64, val: int64) // schreibt f32-Bits in Element i

Elementzugriff. Werte werden als rohe 32-Bit-Bitmuster (in int64) übergeben.

SimdLen

fn SimdLen(arr: int64): int64   // gibt n (Elementanzahl) zurück

Liest die Elementanzahl aus arr-8.


Codebeispiel

import std.cpu.dispatch;
import std.io;

fn main() {
  // Dispatch-Level ermitteln
  var lvl: int64 := CpuDispatchLevel();
  if (lvl == CPU_DISPATCH_AVX2) {
    PrintLn("Dispatch: AVX2");
  } else if (lvl == CPU_DISPATCH_SSE2) {
    PrintLn("Dispatch: SSE2");
  } else {
    PrintLn("Dispatch: Scalar");
  }

  // f32-Array belegen: Bits von 1.0 = 0x3F800000, 2.0 = 0x40000000
  var a: int64 := SimdAlloc(4);
  var b: int64 := SimdAlloc(4);
  SimdSet(a, 0, 0x3F800000);  // 1.0
  SimdSet(a, 1, 0x3F800000);  // 1.0
  SimdSet(a, 2, 0x3F800000);  // 1.0
  SimdSet(a, 3, 0x3F800000);  // 1.0
  SimdSet(b, 0, 0x40000000);  // 2.0
  SimdSet(b, 1, 0x40000000);  // 2.0
  SimdSet(b, 2, 0x40000000);  // 2.0
  SimdSet(b, 3, 0x40000000);  // 2.0

  var result: int64 := SimdAdd(a, b);

  PrintLn(IntToStr(SimdLen(result)) + " Elemente");
  // Elemente als Bits auslesen
  PrintLn("result[0] bits: 0x" + IntToStr(SimdGet(result, 0)));
}

Hinweis: f32-Bits werden als rohe Integerwerte gehandhabt. Zum menschenlesbaren Ausgeben müssen die Bits als f64 interpretiert werden — eine Helper-Unit für f32⟺int64-Konversionen gibt es aktuell nicht in der Stdlib.


Implementierungshinweise

Der Scalar-Fallback (SimdAddScalar u. a.) konvertiert die f32-Bitbitmuster mit as int64 in Integer, dann mit as f64 in Float und rechnet damit. Das ergibt korrekte Werte nur dann, wenn die IEEE-754-Bitmuster und die numerischen Werte übereinstimmen — was bei regulären f32-Zahlen nicht der Fall ist. Zum Beispiel wird 1.0f (Bits 0x3F800000 = 1065353216) als Float 1065353216.0 interpretiert.

Die eigentlichen SIMD-Instruktionen (ADDPS, VADDPS) werden nicht von diesen Stdlib-Funktionen emittiert, sondern vom Compiler-Codegen (CGN_BINOP SIMD), wenn der Compiler f32-Ausdrücke auf SIMD-Arrays erkennt. Diese Stdlib-Funktionen sind das Gerüst für expliziten Library-Code außerhalb des Compiler-optimierten Pfades.


Einschränkungen

Thema Details
Scalar-Fallback buggy f32-Bits werden als Integer-Werte addiert/subtrahiert — kein korrektes f32-Rechnen
SSE2-Pfad = Scalar SimdAdd mit SSE2 ruft intern SimdAddScalar auf (SSE2 via Codegen, nicht stdlib)
Kein SimdFree Manuell per MmapFree freigeben oder Leak akzeptieren
f32 als Bits SimdGet/Set liefert rohe 32-Bit-Bitmuster in int64 — kein Typ-Safety
Linux only Nutzt mmap + CpuFeatureDetect (proc/cpuinfo)
Keine Bounds-Checks SimdGet(arr, i) prüft nicht ob i < SimdLen(arr)
Entwicklungsstand WP-GPU-02; keine pub-Deklarationen; Import-Pfad src.std.*
GPU-Units std.gpu.* existiert noch nicht (WP-GPU-07–09, geplant)

Letzte Aktualisierung: 2026-06-13