====== 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]]