Software Lockstep
Software Lockstep ist ein Mechanismus zur Erkennung von Berechnungsfehlern — verursacht durch kurzzeitige Prozessorstörungen (SEUs in der CPU, Latch-Fehler, Timing-Anomalien). Der Compiler dupliziert kritische Rechenoperationen und vergleicht die Ergebnisse beider Berechnungen vor jedem dauerhaften Speicherzugriff oder return. Stimmen sie nicht überein, greift ein kontrollierter Recovery-Pfad.
Im Unterschied zu Hardware-Lockstep (zwei physische Kerne, synchron getaktet, Hardware vergleicht Ausgaben) läuft Software Lockstep auf einem einzelnen Kern — der Compiler fügt Instruktions-Redundanz in den Maschinencode ein.
→ Verwandt: TMR · Memory Scrubbing · DO-178C Hauptseite · Attribute & Pragmas
Was der Compiler wirklich tut (gemessen mit lyxc 1.1.18A, 2026-09-05, #1908):
* Seit 1.1.15A wirdsoftware_lockstepnachgewiesen — aber schmaler als diese Seite es früher beschrieb: der Rückgabeausdruck wird zweimal gerechnet und vor demretverglichen. Nicht jede ALU-Operation der Funktion.
* Bei Abweichung: Abbruch mit Code 133. Es gibt keinen Recovery-Handler —recovery_ptrsteht im Dateiformat, wird aber nie angesprungen. Ein Wiederherstellungspfad bräuchte eine zweite Codekopie, die es nicht gibt.
* Nur x86-64. Die IR-Backends (lyxos, arm64, riscv, arm-cm4, xtensa) kennen weder Lockstep noch Sweep; dort ist@integritywirkungslos.
1. Aktivierung
Software Lockstep wird per @integrity-Pragma an einer Funktion aktiviert:
@dal(A)
@flight_crit
@integrity(mode: software_lockstep)
@stack_limit(2048)
@wcet(200)
fn ComputeAltitudeError(current: int64, target: int64): int64 {
var delta: int64 := target - current;
var clamped: int64 := delta;
if (clamped > 5000) { clamped := 5000; }
if (clamped < -5000) { clamped := -5000; }
return clamped; // dieser Ausdruck wird zweimal gerechnet und verglichen
}
software_lockstep kennt kein interval. Der Vergleich sitzt vor jedem return und findet immer statt; eine Frist gibt es dort nicht. @integrity(mode: software_lockstep, interval: 50) wird abgewiesen:
Parse error: @integrity: `interval` gilt nur fuer `mode: scrubbed` — `software_lockstep` prueft vor jedem `return`
interval ist ausschließlich für mode: scrubbed zulässig.
An einer unit geht es ebenfalls nicht:
Parse error: @integrity(mode: software_lockstep) gilt nur an einer FUNKTION, nicht an der Unit
2. Was der Compiler generiert
Für eine Funktion mit software_lockstep erzeugt der Compiler folgenden Ablauf. Verdoppelt wird der Rückgabeausdruck, nicht der gesamte Funktionsrumpf:
Quellcode
@integrity(mode: software_lockstep)
fn ComputeSpan(current: int64, target: int64): int64 {
return target - current;
}
Was daraus entsteht (konzeptuell)
var p: int64 := target - current; // primäre Rechnung
var r: int64 := target - current; // redundante Rechnung, andere Register
if (p != r) {
// kein Handler, kein Rücksprung: Abbruch mit Code 133
}
return p;
Primäre und redundante Rechnung laufen in unterschiedlichen physischen Registern, damit ein einzelner Latch-Fehler in einer Registerbank nicht beide gleichzeitig trifft.
Umgesetzt in src/codegen_x86.lyx (cg_lockstepPruefe, cg_lockstepEmitFail); geprüft durch tests/integrity_test.sh.
3. Einschränkungen und Regeln
Zwei Regeln setzt der Compiler selbst durch — er weist ab, statt stillschweigend nichts zu tun:
| Abgewiesen | Meldung | Warum |
|---|---|---|
Rückgabeausdruck mit Wirkung (Aufruf, new, Zuweisung) | der Rueckgabeausdruck hat eine Wirkung […] und laesst sich nicht zweimal rechnen | Zweimal rechnen hieße zweimal wirken |
| Struct- oder Tupel-Rückgabe | Struct-Rueckgabe wird nicht abgedeckt | Es gibt kein einzelnes Register zu vergleichen |
Praktische Folge: return Clamp(x, 0, 1000); übersetzt nicht. Das Ergebnis erst in eine Variable legen und diese zurückgeben.
Die übrigen Punkte sind Entwurfsregeln, keine Compilerprüfungen:
| Regel | Begründung |
|---|---|
Kein @extern fn in lockstep-geschützten Funktionen | Compiler hat keinen Zugriff auf externen Binärcode — kann ihn nicht duplizieren |
@wcet muss etwa 2× den normalen Wert annehmen | Jede Operation wird zweimal ausgeführt |
Kein software_lockstep auf Funktionen mit Seiteneffekten (I/O, HW-Register) | I/O-Operationen dürfen nicht doppelt ausgeführt werden |
Empfohlen: Kombination mit @redundant für Variablen | Schutz der Berechnung (Lockstep) + Schutz des Speichers (TMR) |
Kein software_lockstep auf ISR-Funktionen ohne @no_opt | ISR-Code muss deterministisch bleiben |
// FALSCH — Aufruf im Rückgabeausdruck: wird abgewiesen
@integrity(mode: software_lockstep)
fn BadFunction(sensor: int64): int64 {
return Clamp(sensor * 3 / 2, 0, 1000);
}
// RICHTIG — Ergebnis in eine Variable, dann zurückgeben
@integrity(mode: software_lockstep)
fn ComputeOutput(sensor: int64): int64 {
var v: int64 := Clamp(sensor * 3 / 2, 0, 1000);
return v;
}
fn SendOutput(val: int64): void {
WriteHardwareRegister(UART_TX, val); // I/O außerhalb des Lockstep-Schutzes
}
4. WCET-Interaktion
Der verdoppelte Rückgabeausdruck kostet Laufzeit. Wie viel, hängt davon ab, wie viel Rechnung im return steht — die pauschale Verdopplung der Funktionslaufzeit gilt nicht, weil nur der Rückgabeausdruck doppelt läuft. @wcet entsprechend anpassen:
// Ohne Lockstep: geschätzte WCET 80 µs
@wcet(80)
fn NormalFunction(x: int64): int64 {
return x * x + x / 2;
}
// Mit Lockstep: WCET erhöhen
@integrity(mode: software_lockstep)
@wcet(180) // 2× + Overhead für Vergleich und Recovery-Pfad
fn ProtectedFunction(x: int64): int64 {
return x * x + x / 2;
}
Der Compiler zeigt bei fehlerhaft gesetztem @wcet auf lockstep-Funktionen eine Warnung:
warning[W0244]: @wcet(80) on software_lockstep function ComputeControlOutput
Estimated protected WCET: 167 µs exceeds declared budget: 80 µs
Consider: @wcet(180) or higher
5. Vergleich der Integritätsmodi
Es gibt zwei Modi. mode: hardware_ecc wird abgewiesen (nicht umgesetzt — es gibt keinen Traeger dafuer); die Grammatik nennt den Namen, der Compiler kennt ihn nicht.
| Merkmal | software_lockstep | scrubbed |
|---|---|---|
| Schützt | den Rückgabeausdruck einer Funktion | die geladenen Codeseiten |
| Erkennt | Rechenfehler vor dem ret | Bit-Flips im Code zur Laufzeit |
interval | unzulässig | 1…3600000 ms, Vorgabe 1000 |
| Abbruchcode | 133 | 135 (Codeseite), 136 (keine Mehrheit der drei Hashkopien) |
| Anbringung | nur an einer Funktion | Funktion; an der unit derzeit ebenfalls angenommen |
| Laufzeit | doppelter Rückgabeausdruck | Sweep im Hintergrund (SIGALRM) |
| Komb. mit @redundant | Empfohlen | Nicht nötig |
6. Was bei einer Abweichung passiert
Das Programm bricht ab, Code 133. Es gibt keinen Handler und keine Rückkehr in den normalen Ablauf.
SetLockstepRecoveryHandler und LockstepFault existieren nicht — beide melden undefined function. Frühere Fassungen dieser Seite zeigten sie als Beispiel. Was es gibt, ist VerifyIntegrity() aus std.meta_safe: eine Prüfung auf Zuruf über die eigene Datei, kein Lockstep-Handler.
Im Dateiformat steht ein Feld recovery_ptr (Offset 48). Es ist belegt, wird aber nicht angesprungen: ein Wiederherstellungspfad bräuchte eine zweite Codekopie, die es nicht gibt. Der Mechanismus erkennt, er korrigiert nicht.
Wer einen kontrollierten Notfallpfad braucht, baut ihn außerhalb: Watchdog auf Systemebene, Umschalten auf einen Backup-Rechner, Auswertung des Abbruchcodes durch den Aufrufer. Innerhalb des Prozesses gibt es dafür heute keinen Ansatzpunkt.
7. DAL-Empfehlung
| DAL | software_lockstep | Begründung |
|---|---|---|
| A | Zwingend für Kernberechnungen | Katastrophaler Ausfall — maximaler Schutz |
| B | Empfohlen | Gefährlicher Ausfall — Investition gerechtfertigt |
| C | Optional | WCET-Impact meist nicht vertretbar |
| D/E | Nicht sinnvoll | Overhead ohne Sicherheitsgewinn |
Letzte Aktualisierung: 2026-09-05 — Seite auf den gemessenen Compilerstand gezogen (#1908, gemessen mit lyxc 1.1.18A, 2026-09-05): interval aus allen sechs Aufrufen entfernt (an software_lockstep abgewiesen), der erklärende Absatz zum interval-Polling gestrichen (beschriebenes Verhalten gibt es nicht), das Unit-Ebene-Beispiel entfernt (wird abgewiesen), Spalte hardware_ecc aus der Modus-Tabelle genommen (Modus wird abgewiesen), Abschnitt 6 ersetzt: SetLockstepRecoveryHandler und LockstepFault existieren nicht, bei Abweichung bricht das Programm mit 133 ab. Zwei Compilerprüfungen ergänzt (Rückgabeausdruck mit Wirkung, Struct-Rückgabe) und die Beispiele danach berichtigt.
Vorherige Aktualisierung: 2026-05-22
Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).
