Inhaltsverzeichnis

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 wird software_lockstep nachgewiesen — aber schmaler als diese Seite es früher beschrieb: der Rückgabeausdruck wird zweimal gerechnet und vor dem ret verglichen. Nicht jede ALU-Operation der Funktion.
* Bei Abweichung: Abbruch mit Code 133. Es gibt keinen Recovery-Handler — recovery_ptr steht 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 @integrity wirkungslos.

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).