====== 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 [[lyx_-_programmiersprache:units:cpu:features|std.cpu.features]]. → [[lyx_-_programmiersprache:units|Standard Library]] · [[lyx_-_programmiersprache:units:cpu:features|std.cpu.features]] · [[lyx_-_programmiersprache:start|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|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: - liest ''n'' aus ''src1-8'' - allokiert ein neues Array (''SimdAlloc(n)'') - rechnet und schreibt in das neue Array - 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