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 einlimitoder durch einen statisch beweisbaren Abbruch terminieren. Einewhile (true)- Schleife ohnelimiterzeugt 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 istwhile (true)ohnelimitverboten.
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:
