====== Lyx – Schleifen (Loops) ====== Lyx bietet drei Schleifenformen für unterschiedliche Anwendungsfälle: ''while'' für bedingungsgesteuerte Schleifen, ''for'' für Zählschleifen mit bekanntem Bereich und ''repeat-until'' für fußgesteuerte Schleifen. Für sicherheitskritischen Code ergänzt das ''limit''-Keyword jede Schleife um eine garantierte Abbruchbedingung – eine Voraussetzung für WCET-Analyse und DO-178C-Zertifizierung. ===== 1. while – Bedingungsgesteuerte Schleife ===== ''while'' prüft die Bedingung **vor** jedem Durchlauf. Der Block wird ausgeführt, solange die Bedingung ''true'' ergibt. Ergibt sie beim ersten Test ''false'', wird der Block kein einziges Mal ausgeführt. unit schleifen; import std.io; fn main(): int64 { var x: int64 := 0; while (x < 5) { PrintInt(x); // 0 1 2 3 4 x++; } return 0; } ==== Typische Verwendung ==== // Warten auf eine externe Bedingung while (!DeviceReady()) { Sleep(10); } // Eingabe-Validierungs-Schleife var input: int64 := ReadInt(); while (input < 0 | input > 100) { PrintStr("Ungültig. Bitte 0–100 eingeben:"); input := ReadInt(); } ===== 2. while – Bounded Loop (limit) ===== In sicherheitskritischen Bereichen muss jede Schleife **nachweislich terminieren**. Das ''limit''-Keyword setzt eine harte Obergrenze für die Anzahl der Iterationen. Auch wenn die Bedingung noch ''true'' wäre, bricht die Schleife nach N Durchläufen garantiert ab. **Syntax:** ''while (Bedingung) limit(Konstante) { ... }'' @flight_crit fn WaitForSensor(): bool { var ready: bool := false; while (!ready) limit(500) { ready := PollSensor(); } return ready; } > **Zertifizierungs-Hinweis (DO-178C):** > In Modulen mit ''@dal(A)'' oder ''@dal(B)'' prüft der Compiler mit ''--stack-check'', ob alle Schleifen entweder durch ein ''limit'' oder durch einen statisch beweisbaren Abbruch terminieren. Eine ''while (true)''- Schleife ohne ''limit'' erzeugt in diesen Modulen einen **Compiler-Fehler**. ==== limit in Kombination mit break ==== ''limit'' wirkt zusätzlich zum normalen Abbruch durch ''break'' oder das Ende der Bedingung. Die Schleife endet beim **ersten** dieser Ereignisse. var found: int64 := -1; var i: int64 := 0; while (i < len(buffer)) limit(1024) { if (buffer[i] = 0xFF) { found := i; break; } i++; } ===== 3. for – Zählschleife ===== ''for'' ist für Schleifen mit bekanntem Zählbereich optimiert. Die Laufvariable ist innerhalb des Blocks sichtbar und wird automatisch inkrementiert bzw. dekrementiert. ==== Aufwärts zählen (to) ==== for i := 0 to 9 do { PrintInt(i); // 0 1 2 3 4 5 6 7 8 9 } Die Obergrenze ist **inklusive**: ''for i := 0 to 9'' erzeugt genau 10 Durchläufe (i = 0, 1, …, 9). ==== Abwärts zählen (downto) ==== for i := 10 downto 1 do { PrintInt(i); // 10 9 8 7 6 5 4 3 2 1 } ==== for ist immer bounded ==== Da Ober- und Untergrenze beim Eintritt in die Schleife feststehen, ist eine ''for''-Schleife **immer terminierend** – kein ''limit'' erforderlich. Der Compiler erkennt dies automatisch und gibt keine WCET-Warnung aus. ==== Array-Iteration (index-basiert) ==== var temps: [7]f64; temps[0] := 20.1; temps[1] := 21.3; temps[2] := 19.8; temps[3] := 22.0; temps[4] := 23.5; temps[5] := 20.9; temps[6] := 21.7; var sum: f64 := 0.0; for i := 0 to 6 do { sum := sum + temps[i]; } PrintFloat(sum / 7.0); // Durchschnittstemperatur ==== Verschachtelte for-Schleifen ==== // Matrix-Multiplikation (vereinfacht, 3×3) var a: [3][3]f64; var b: [3][3]f64; var c: [3][3]f64; for i := 0 to 2 do { for j := 0 to 2 do { var acc: f64 := 0.0; for k := 0 to 2 do { acc := acc + a[i][k] * b[k][j]; } c[i][j] := acc; } } ===== 4. repeat-until – Fußgesteuerte Schleife ===== ''repeat-until'' prüft die Abbruchbedingung **nach** jedem Durchlauf. Der Block wird mindestens einmal ausgeführt, auch wenn die Bedingung beim ersten Test ''true'' wäre. var choice: int64; repeat { PrintStr("Menü: 1=Start, 2=Stop, 3=Exit"); choice := ReadInt(); } until (choice >= 1 & choice <= 3); ==== Vergleich: while vs. repeat-until ==== ^ Kriterium ^ while ^ repeat-until ^ | Prüfzeitpunkt | Vor dem Körper (kopfgesteuert) | Nach dem Körper (fußgesteuert) | | Min. Durchläufe | 0 | 1 | | Typischer Einsatz | Warte-Schleifen, Stream-Verarbeitung | Menüs, Eingabe-Validierung, Protokoll-Handshake | | Bounded (limit) | ✅ möglich | ✅ möglich | ==== repeat-until mit limit ==== @flight_crit fn Handshake(): bool { var ack: bool := false; repeat { SendPing(); ack := WaitAck(); } until (ack) limit(10); return ack; } ===== 5. break & continue – Schleifensteuerung ===== ==== break – Schleife sofort beenden ==== ''break'' verlässt die innerste laufende Schleife sofort und setzt die Ausführung hinter der schließenden ''}'', fort. var result: int64 := -1; var i: int64 := 0; while (i < 100) { if (IsPrime(i)) { result := i; break; // Erstes Ergebnis gefunden – fertig } i++; } PrintInt(result); ==== continue – Iteration überspringen ==== ''continue'' springt direkt zum nächsten Schleifendurchlauf und überspringt den restlichen Körper der aktuellen Iteration. for i := 0 to 99 do { if (i mod 2 = 0) { continue; } // Gerade Zahlen überspringen PrintInt(i); // Gibt nur ungerade Zahlen aus: 1 3 5 … 99 } ==== Verschachtelte Schleifen: break bricht nur die innerste Schleife ==== var found: bool := false; for row := 0 to 9 do { for col := 0 to 9 do { if (matrix[row][col] = 0) { found := true; break; // Verlässt nur die innere (col-)Schleife } } if (found) { break; } // Verlässt die äußere (row-)Schleife } ===== 6. Endlosschleifen – Hauptschleife (Event Loop) ===== Eine ''while (true)''-Schleife ohne ''limit'' repräsentiert eine **Endlosschleife**, die typischerweise als Hauptschleife (Event Loop, Control Loop) in Embedded-Systemen und Servern eingesetzt wird. fn main(): int64 { SystemInit(); while (true) { // Hauptschleife – läuft bis Power-Off var event := PollEvent(); HandleEvent(event); UpdateOutputs(); } return 0; } > **In @flight_crit- und @dal(A/B)-Modulen ist ''while (true)'' ohne ''limit'' verboten.** > Die Hauptschleife eines Steuergeräts lebt typischerweise im ''@dal(C)''-Hauptmodul; die aufgerufenen Handler-Funktionen tragen die höheren DAL-Attribute. ===== 7. @parallel – SIMD-Vektorisierung von Schleifen ===== Das ''@parallel''-Pragma weist den Compiler an, eine Schleife zu vektorisieren (AVX2 auf x86_64, NEON auf ARM64). Voraussetzung: keine Datenabhängigkeiten zwischen den Iterationen. unit dsp; fn ScaleSignal(samples: [1024]f32, gain: f32) { @parallel for i := 0 to 1023 do { samples[i] := samples[i] * gain; // 8 Iterationen pro AVX2-Instruktion } } fn Threshold(data: [512]f64, limit_val: f64): [512]bool { var result: [512]bool; @parallel for i := 0 to 511 do { result[i] := data[i] > limit_val; } return result; } ''@parallel'' ist **nicht** zulässig, wenn Iterationen voneinander abhängen (z. B. Akkumulation in ''sum := sum + x[i]'') – der Compiler warnt in diesem Fall. ===== 8. Energy-Aware Loop Unrolling ===== Der Compiler passt das **Loop Unrolling** automatisch an das Energy-Level der umgebenden Funktion an. Unrolling reduziert den Branch-Overhead, vergrößert aber den Code. ^ Energy-Level ^ Unrolling-Faktor ^ Beschreibung ^ | 1 (Minimal) | 2× | Minimaler Code-Footprint, schont I-Cache | | 2 | 4× | Leichtes Unrolling, geringer Overhead | | 3 (Standard) | 4× | Ausgewogen – Standard ohne @energy-Pragma | | 4 | 8× | Aggressives Unrolling, nutzt mehrere Ausführungseinheiten | | 5 (Extreme) | 8× + SIMD | Maximaler Durchsatz; auch ohne @parallel vektorisiert | @energy(5) fn ProcessHighFreq(samples: [256]f32) { for i := 0 to 255 do { samples[i] := samples[i] * 2.0f32; } // Compiler unrollt 8× und kombiniert mit SIMD: ~32 Iterationen à 8 Elemente } @energy(1) fn LowPowerScan(data: [64]uint8): int64 { var count: int64 := 0; for i := 0 to 63 do { if (data[i] != 0u8) { count++; } } return count; // Compiler unrollt 2× – minimal; spart Code-Größe und I-Cache } ===== 9. Schleifen in Safety-Code ===== ^ Schleifentyp ^ @flight_crit ^ @dal(A) ^ @dal(B) ^ @dal(C/D) ^ | ''for'' (bekannte Grenzen) | ✅ | ✅ | ✅ | ✅ | | ''while'' mit ''limit'' | ✅ | ✅ | ✅ | ✅ | | ''repeat-until'' mit ''limit'' | ✅ | ✅ | ✅ | ✅ | | ''while'' ohne ''limit'' | ❌ Fehler | ❌ Fehler | ❌ Fehler | ⚠️ Warnung | | ''while (true)'' ohne ''limit'' | ❌ Fehler | ❌ Fehler | ❌ Fehler | ⚠️ Warnung | | ''@parallel''-Schleife | ❌ Verboten | ❌ Verboten | ⚠️ Warnung | ✅ | ==== WCET-Annotation ==== Der ''--stack-check''- und ''--wcet''-Pass analysiert die Iterationszahl aller Schleifen: $ lyxc --wcet --call-graph main.lyx Loop-Analyse: WaitForSensor (while, limit=500) → max. 500 Iter. ✅ beschränkt Handshake (repeat, limit=10) → max. 10 Iter. ✅ beschränkt ScaleSignal (for, 0..1023) → exakt 1024 Iter. ✅ statisch bekannt MainLoop (while true, kein limit) → unbeschränkt ✗ @dal(A): Fehler ===== 10. Typische Schleifenmuster ===== ==== Lineare Suche ==== fn Find(arr: [256]int64, target: int64): int64 { for i := 0 to 255 do { if (arr[i] = target) { return i; } } return -1; } ==== Akkumulation / Reduktion ==== fn Sum(arr: [64]int64): int64 { var total: int64 := 0; for i := 0 to 63 do { total := total + arr[i]; } return total; } fn Max(arr: [64]int64): int64 { var best := arr[0]; for i := 1 to 63 do { if (arr[i] > best) { best := arr[i]; } } return best; } ==== Gleitender Mittelwert (Sliding Window) ==== fn MovingAvg(data: [128]f64, window: int64): [128]f64 { var result: [128]f64; for i := 0 to 127 do { var sum: f64 := 0.0; var count: int64 := 0; var j := i - window + 1; if (j < 0) { j := 0; } while (j <= i) { sum := sum + data[j]; count++; j++; } result[i] := sum / count as f64; } return result; } ==== Ringpuffer-Drain ==== fn DrainBuffer(buf: ^uint8, head: int64, tail: int64, size: int64): int64 { var processed: int64 := 0; while (head != tail) limit(4096) { ProcessByte(buf[head]); head := (head + 1) mod size; processed++; } return processed; } ===== 11. Vollständiges Beispiel: Sensor-Datenfilter ===== Ein typischer Embedded-Regelkreis kombiniert alle Schleifenformen: unit sensor_filter; import std.io; import std.math; con SAMPLE_COUNT: int64 := 64; con THRESHOLD: f64 := 50.0; con MAX_RETRIES: int64 := 10; @flight_crit @dal(C) @stack_limit(2048) fn AcquireAndFilter(): f64 { var samples: [64]f64; // 1. Daten erfassen – repeat-until: mindestens ein Versuch var ok: bool := false; repeat { ok := SensorReady(); } until (ok) limit(MAX_RETRIES); if (!ok) { return -1.0; } // Sensor nicht bereit // 2. Samples einlesen – for: exakt bekannte Anzahl for i := 0 to SAMPLE_COUNT - 1 do { samples[i] := ReadSensor(); } // 3. Ausreißer entfernen – for mit continue var sum: f64 := 0.0; var count: int64 := 0; for i := 0 to SAMPLE_COUNT - 1 do { if (samples[i] < 0.0 | samples[i] > THRESHOLD) { continue; // Ausreißer überspringen } sum := sum + samples[i]; count++; } if (count = 0) { return -1.0; } return sum / count as f64; } fn main(): int64 { SystemInit(); // 4. Haupt-Regelschleife – while ohne limit (im @dal(C)-Kontext erlaubt) while (true) { var value := AcquireAndFilter(); if (value >= 0.0) { PrintFloat(value); SetActuator(value); } else { PrintStr("Sensorfehler"); } Sleep(100); } return 0; } ===== 12. Zusammenfassung ===== ^ Schleifentyp ^ Prüfung ^ Min. Durchläufe ^ Bounded ^ Typischer Einsatz ^ | ''while (cond)'' | Vor dem Körper | 0 | ''limit(N)'' | Warten, Stream-Verarbeitung | | ''while (true)'' | – | ∞ | ''limit(N)'' | Event-Loop, Hauptschleife | | ''for i := a to b'' | Vor dem Körper | 0 (wenn a > b) | Immer (b-a+1) | Array-Iteration, Zählschleifen | | ''for i := b downto a'' | Vor dem Körper | 0 (wenn b < a) | Immer (b-a+1) | Rückwärts-Iteration | | ''repeat-until'' | Nach dem Körper | **1** | ''limit(N)'' | Eingabe, Handshake, Protokoll | **Weiterführende Seiten:** * [[lyx_-_programmiersprache:rekursion|Rekursion – Schleifen vs. rekursive Aufrufe]] * [[lyx_-_programmiersprache:arrays-datensammlungen|Arrays & Datensammlungen – Iteration über Sammlungen]] * [[lyx_-_programmiersprache:das-energy-aware-programmiermodell|Energy-Aware Programmiermodell – Loop Unrolling & SIMD]] * [[lyx_-_programmiersprache:do-178c|DO-178C Compliance – WCET-Analyse und Schleifenlimits]] * [[lyx_-_programmiersprache:pointer-inlining|Pointer & Inlining – @parallel und @no_opt]]