Lyx – Attribute & Pragmas (@-Referenz)

Attribute (auch Pragmas genannt) sind Compiler-Anweisungen, die mit @ beginnen und vor einer Deklaration stehen. Sie steuern Optimierungen, Sicherheits-Verifikation, Speicherlayout und FFI-Verhalten. Attribute verändern nie die Semantik des Programms für korrekte Eingaben — sie sind Zusatzinformationen für den Compiler, nicht für den Laufzeit-Interpreter.

Compiler-Flags (CLI) · DO-178C Compliance · Energy-Aware Modell · FFI – C-Interop


Übersicht

Alle Attribute auf einen Blick:

Attribut Ziel Kategorie Beschreibung
@dal(A–D) Unit, Fn Safety Design Assurance Level nach DO-178C
@flight_crit Unit, Fn Safety Strikte Luftfahrt-Regeln
@critical Fn Safety Als kritisch markieren
@integrity(mode: …) Fn (scrubbed auch vor unit) Safety mode: scrubbed (Sweep über die geladenen Codeseiten) oder mode: software_lockstep (Rückgabeausdruck doppelt gerechnet)
@redundant Variable Safety Triple Modular Redundancy (TMR)
@stack_limit(N) Fn Safety Maximaler Stack-Verbrauch in Bytes
@wcet(N) Fn Safety Worst-Case Execution Time in Zyklen
@energy(1–5) Unit, Fn Performance Energy-Aware-Backend-Level
@packed Struct Layout Struct-Padding unterdrücken
@align(N) Variable Layout Allokations-Alignment in Bytes
@big_endian Struct-Feld Layout Feld liegt in Network Byte Order; Zugriffe erzeugen einen Byte-Swap. @little_endian gibt es am Feld nicht — siehe unten
@volatile Variable FFI Speicherzugriff nicht wegoptimieren
@description / @author / @copyright / @version Modulkopf Doku Beschreibende Angaben vor unit/import

Prüfstand: was jedes Attribut mit 1.1.14A tatsächlich tut

Jedes Attribut einzeln übersetzt, jeweils gegen dasselbe Programm ohne Attribut verglichen (Rückgabewert, Meldungen, Größe des Erzeugnisses).

Attribut Angenommen Wirkung, gemessen
@dal(A)@dal(D) ja Warnung: „@dal: die Zusicherung wird vom Compiler NICHT nachgewiesen„. Erzeugnis unverändert. @dal(Z) bricht ab: „Stufe muss A, B, C oder D sein“
@flight_crit ja, ohne Warnung Erzeugnis wächst (4243 → 4327 Byte) — das Attribut schaltet tatsächlich etwas frei (FPU-Traps, #1140)
@critical ja Warnung: wird nicht nachgewiesen. Erzeugnis unverändert
@integrity(mode: scrubbed), @integrity(mode: software_lockstep) ja wird nachgewiesen (seit 1.1.15A): Hashtabelle METASAF2 plus SIGALRM-Sweep bzw. doppelt gerechneter Rückgabeausdruck. Nur x86-64. Die Kurzformen ohne mode: und der Modusname memory_scrubbing werden abgewiesen
@redundant ja Wirkt: --verify-tmr meldet „1 @redundant-Variable(n), 1 gevotete® Lesezugriff(e), 0 am Voter vorbei„
@stack_limit(N) ja Wird nachgewiesen und bricht ab: @stack_limit(8) an einer Funktion mit 16-Byte-Rahmen → „@stack_limit(8) verletzt — der Rahmen belegt 16 Byte“. Gezählt wird der Rahmen, nicht statisch abgelegte Felder
@wcet(N) ja Wird nachgewiesen: @wcet(1) an einer Schleife mit 1000 Durchläufen → „@wcet(1) verletzt — der Rumpf laeuft bis zu 1000 Iterationen„. Ein Aufruf ohne Schranke bricht ebenfalls ab: „PrintLn liegt nicht in dieser Einheit und traegt keine Schranke“
@energy(1–5) ja Übersetzt; Erzeugnis in diesem Prüffall unverändert
@packed ja Wirkt: struct{int16,int8,int16} misst 5 statt 6 Byte
@align(N) ja Übersetzt an einer lokalen Variablen
@big_endian ja, an jedem Feld Byte-Swap bei jedem Zugriff
@little_endian nein Parse error, auch am ersten Feld (#1864)
@volatile ja Übersetzt an einer lokalen Variablen
@description, @author, @copyright, @version ja Übersetzt; reine Metadaten
 
Die Warnung bei @dal, @critical und @integrity ist die ehrliche Auskunft, kein Mangel. Der Compiler sagt damit: das Attribut dokumentiert eine Zusicherung, er prüft sie nicht. Nachgewiesen werden nur @stack_limit, @wcet und — mit --verify-tmr@redundant. Wer die drei anderen in einem Zertifizierungsnachweis führt, braucht dafür ein Verfahren außerhalb des Compilers.

Externe Funktionen werden ohne Attribut deklariert — mit dem Schlüsselwort extern (siehe FFI).

 
Diese Attribute gibt es nicht. Frühere Fassungen dieser Seite führten sie mit eigenen Abschnitten; der Compiler weist jeden unbekannten Attributnamen seit #1099 mit unbekanntes Attribut ab:

@inline · @noinline · @no_opt · @parallel · @section(„name“) · @at(N) · @stdcall · @variadic · @allow_unsafe

@export gehört nicht in diese Liste — es existiert und wird an extern fn angenommen. (Issue #1247)

Damit entfällt auch die Vorstellung, Inlining oder Vektorisierung ließen sich per Annotation steuern — das Backend entscheidet das selbst, beeinflussbar allein über @energy und –target-energy.
@allow_unsafe hätte unsafe-Blöcke freigeben sollen; auch die gibt es nicht (undefined symbol '\unsafe'\).

Safety-Attribute

Attribute für sicherheitskritischen und zertifizierungspflichtigen Code:

@dal(A–D)

Legt das Design Assurance Level nach DO-178C fest. Beeinflusst, welche Lint-Prüfungen aktiv sind und ob bestimmte Sprachfeatures verboten werden.

@dal(A)
unit flight_control;

@dal(B)
fn ProcessSensorData(raw: int64): int64 { ... }

Level Bedeutung Einschränkungen
@dal(A) Katastrophaler Ausfall möglich Kein unbounded loop, exhaustive match
@dal(B) Gefährlicher Ausfall möglich Unbounded loops mit Warnung
@dal(C) Größerer Ausfall möglich Empfehlungen, keine harten Verbote
@dal(D) Geringfügiger Ausfall möglich Nur Dokumentation, keine Compiler-Einschränkungen

@flight_crit

Aktiviert den strengsten Kompilierungsmodus für sicherheitskritische Funktionen:

@flight_crit
@dal(A)
@stack_limit(512)
fn ComputeFlightPath(state: int64): FlightCommands { ... }

Wirkung zur Laufzeit: Der Prolog der Funktion schaltet die SSE-Ausnahmen für invalid (NaN) und divide-by-zero frei und sichert MXCSR, der Epilog stellt den alten Wert wieder her. Eine entstehende NaN oder Inf löst damit SIGFPE aus statt still weiterzurechnen; der mitgegebene Handler meldet panic: FPU-Ausnahme (NaN/Inf oder Division durch 0) unter @flight_crit in `NAME` und endet mit 134. Die Reichweite ist dynamisch: MXCSR ist Thread-Zustand, der Trap gilt also auch für alles, was die Funktion ruft, bis sie zurückkehrt. Überlauf, Unterlauf und Ungenauigkeit bleiben maskiert.

Impliziert automatisch:

  • @energy(1) — kein aggressives Unrolling, keine SIMD-Spekulation
  • keine FPU-Optimierungen für diese Funktion (ein globales Flag dafür gibt es nicht)
  • Verbot von new/alloc im Rumpf
  • Verbot von Rekursion ohne @stack_limit
  • Exhaustive match über alle Enum-Werte (kein default in DAL-A)

Das Speicherverbot ist seit lyxc 1.0.21B durchgesetzt (#1529) und meldet, gemessen mit 1.1.3I:

sema error: Speicheranforderung unter @flight_crit nicht erlaubt 'alloc'
sema error: new ist unter @flight_crit nicht erlaubt — der Speicher muss ausserhalb angelegt und hereingereicht werden
sema error: lokales Feld unter @flight_crit nicht erlaubt (es entsteht per mmap, nicht auf dem Stapel)

 
Die Prüfung reicht seit lyxc 1.1.4A über Aufrufgrenzen hinweg (#1701, nachgemessen mit 1.1.11B). Ruft eine @flight_crit-Funktion einen Helfer, der alloc benutzt, meldet der Compiler:

sema error: Speicheranforderung in einer Funktion, die aus einer @flight_crit-Funktion gerufen wird 'alloc'

Die Prüfung folgt der Aufrufkette mit Zyklusschutz — auch über zwei und mehr Ebenen — und kennt neben new/alloc auch die allozierenden Zeichenketten-Funktionen (IntToStr, StrConcat, StrNew, StrCopy, StrSub, StrTrim, FloatToStr).

Ein Aufruf über einen Funktionszeiger wird jetzt rundheraus abgewiesenindirekter Aufruf unter @flight_crit nicht erlaubt — das Sprungziel steht erst zur Laufzeit fest und laesst sich nicht nachweisen. Was sich nicht nachweisen lässt, gilt damit nicht mehr als unauffällig.

Kompatibel mit @energy(2) wenn explizit annotiert (zulässig für DAL-B):

@flight_crit
@energy(2)   // energy-Barriere: überschreibt das implizite @energy(1)
@dal(B)
fn FilterAltitude(raw: f64): f64 { ... }


@integrity(...)

Aktiviert Laufzeit-Integritätsprüfung. Zwei Modi:

// Modus 1: Memory Scrubbing (periodischer CRC32-Sweep des Code-Segments)
@integrity(mode: scrubbed, interval: 100)
unit nav.core;

// Modus 2: Software Lockstep (duplizierte ALU-Operationen, Vergleich vor Return)
@integrity(mode: software_lockstep)
@dal(A)
fn ComputeHeading(imu: int64): f64 { ... }

Parameter Typ Beschreibung
mode: scrubbed Vorgesehen: CRC32-Sweep des Code-Segments alle interval ms
mode: software_lockstep Vorgesehen: Berechnungen doppelt ausführen, Ergebnis vergleichen
interval: N int (ms) Sweep-Intervall für scrubbed-Modus
 
@integrity hat heute keine Wirkung (#1878). Der Compiler nimmt das Attribut an, meldet warning: @integrity: die Zusicherung wird vom Compiler NICHT nachgewiesen — und erzeugt ein byte-gleiches Programm (4243 Byte mit scrubbed, mit software_lockstep und ohne Attribut, gemessen mit 1.1.14A). Kein Sweep, keine doppelte Berechnung, kein Recovery-Handler.

Auch die .meta_safe-Sektion entsteht dadurch nicht — die gibt es nur mit --meta-safe auf der Kommandozeile.

Ursache im Quelltext: src/ir/ir_safety.lyx trägt die Pragma-Logik, wird aber nirgends importiert oder gebaut. Das erklärt, warum @integrity, @critical und @dal allesamt nur warnen, während @stack_limit und @wcet — die im Codegen sitzen — tatsächlich nachgewiesen werden.

→ Details: Memory Scrubbing · Software Lockstep


@redundant

Speichert eine Variable dreifach im RAM (Sektionen .tmr_a, .tmr_b, .tmr_c). Jeder Lesezugriff führt einen Mehrheitsentscheid durch; ein korrumpiertes Exemplar wird automatisch repariert.

@redundant
var flight_mode: int64 := 0;

@redundant
var altitude_m: f64 := 0.0;

  • Nur für globale und Unit-Level-Variablen; Stack-Variablen werden vom Compiler abgelehnt
  • Speicherbedarf: 3× Variablengröße plus Alignment
  • Die Adresse-von-Form @x liefert die Adresse einer Kopie und umgeht damit den Voter — ein Schreibzugriff darüber geht beim nächsten Mehrheitsentscheid verloren. Der Compiler meldet das: unter --verify-tmr als Fehler, sonst als Warnung
  • --verify-tmr druckt die Bilanz (Variablen, gevotete Lesezugriffe, dreifache Schreibzugriffe, Umgehungen) und endet mit Exit 1, sobald ein Zugriff am Voter vorbeigeht

→ Details: TMR


@stack_limit(N)

Setzt ein hartes Limit für den Stack-Verbrauch einer Funktion in Bytes. Wird bei jeder Übersetzung geprüft — ein Flag dafür gibt es nicht und ist nicht nötig. Geprüft werden zwei Dinge: die Rahmengröße der Funktion selbst gegen die Schranke, und dass die Funktion nicht rekursiv ist (auch nicht indirekt über den Aufrufgraphen) — ohne nachweisbare Aufruftiefe wäre der Verbrauch unbeschränkt. Beides sind harte Fehler. Nicht erfasst: der Verbrauch der aufgerufenen Funktionen entlang der Aufrufkette und dynamisch angeforderter Speicher. Zu beachten: ein [N]int64 liegt nicht im Rahmen — Arrays bekommen einen Heap-Block, der Rahmen wächst nur um 8 Byte je Variable.

@stack_limit(2048)
fn ParsePacket(buf: pchar, len: int64): bool { ... }

@stack_limit(128)
@flight_crit
fn SysTick_Handler(): void { ... }   // ISR: sehr wenig Stack erlaubt

  • Bei Überschreitung: Compiler-Fehler (kein Laufzeitfehler)
  • Rekursion ohne nachweisbare Schranke → automatischer Fehler

@wcet(N)

Gibt die maximal erlaubte Worst-Case Execution Time in Iterationen an — nicht in CPU-Zyklen. Eine Zyklenzahl bräuchte ein Mikroarchitekturmodell; Iterationen sind das, was sich am Syntaxbaum abzählen lässt.

@wcet(200)
@dal(A)
fn ReadIMU(buf: int64): void { ... }

@wcet(500)
fn FilterAltitude(raw: f64): f64 { ... }

  • Der Compiler zählt die Iterationen kumulativ über Loop-Bounds und Aufrufgraph: eine Schleife mit Schranke B, in deren Rumpf I Iterationen stecken, trägt B * (1 + I) bei — zwei geschachtelte Zehnerschleifen ergeben 110
  • Abzählbar sind for … to/downto und for i in a..b mit literalen Grenzen, for i in range(A, B), while © limit(N) und while (i < C) mit literalem Startwert, konstanter Grenze und genau einer Fortschaltung i := i + K, K > 0
  • Alles andere ist in einer @wcet-Funktion ein Fehler, kein stiller Durchlass: berechnete Schleifengrenzen, repeat/until, das C-artige for, Rekursion (auch indirekt) und der Aufruf einer Funktion ohne eigene Schranke — importierte Funktionen eingeschlossen, PrintLn also auch
  • Software-Lockstep verdoppelt die effektive WCET (~2×)
  • Ein --wcet-Flag gibt es nicht — der Nachweis läuft bei jeder Übersetzung. Für den Aufrufgraphen --call-graph verwenden

@energy(1–5)

Steuert das Energy-Aware-Backend für diese Funktion. Es überschreibt das globale –target-energy-Level und erzeugt eine Energy-Barriere — inlinierte Aufrufer übernehmen das Level dieser Funktion nicht.

Die Vorrangregel lautet seit lyxc 1.0.15C:

  1. @energy(N) an der Funktion
  2. @energy(N); als Direktive im Quelltext
  3. –target-energy=N auf der Kommandozeile
  4. Vorgabe 3

Der Quelltext schlägt also das Flag. Zu beachten: Funktionen ohne Attribut — auch die aus importierten Units — folgen weiterhin dem Flag; ein Programm ist deshalb mit und ohne –target-energy nur dann byteidentisch, wenn jede beteiligte Funktion annotiert ist.

@energy(1)
fn IdleSensor(): void { ... }     // Batterie-optimiert

@energy(5)
fn MatMulKernel(a: int64, b: int64, out: int64): void { ... }  // Max-Performance

→ Details und Level-Tabelle: Energy-Aware Modell


@packed

Unterdrückt Alignment-Padding in einer Struct-Definition. Felder folgen ohne Lücken aufeinander. Notwendig für Hardware-Register-Maps, Netzwerk-Paket-Header, binäre Protokolle.

Es gibt zwei gleichwertige Schreibweisen — das Attribut vor der Deklaration oder das Schlüsselwort packed vor struct:

@packed
type CANFrame = struct {
    id:   int32;   // 4 Bytes (11-Bit-ID + Flags)
    dlc:  int8;    // 1 Byte  (Data Length Code)
    data: int64;   // 8 Bytes (Nutzdaten)
}

type IpFragment = packed struct {
    ident:    int16;
    frag_off: int16;
    ttl:      int8;
}

Felder stehen ohne var und die Struktur braucht die Form type Name = struct. Ein alleinstehendes class Name { … } wie in C++ oder Java weist der Compiler mit unexpected top-level token ab.

Die Wirkung lässt sich mit sizeof nachmessen:

type Locker = struct        { a: int16; b: int8; c: int16; }
type Dicht  = packed struct { a: int16; b: int8; c: int16; }

fn main(): int64 {
    PrintLn("struct        = ", IntToStr(sizeof(Locker)));
    PrintLn("packed struct = ", IntToStr(sizeof(Dicht)));
    return 0;
}

struct        = 6
packed struct = 5

Fünf Nutzbytes gegen sechs: die reguläre Ausrichtung legt jedes Feld auf ein Vielfaches seiner eigenen Breite (int16 auf 2, int8 auf 1) — a auf 0, b auf 2, ein Füllbyte, c auf 4. Das entspricht dem Speicherbild eines C-Structs. Bis 1.0.21A lag jedes Feld auf einer 8-Byte-Grenze (#1516), derselbe Struct maß dort 18 Byte; ältere Fassungen dieser Seite zeigen diese Zahl. packed bleibt für Protokollköpfe richtig, wo auch das eine Füllbyte stört — nachgemessen mit 1.1.14A.

  • Unaligned-Zugriffe können auf manchen Architekturen (ARM Cortex-M0) Faults auslösen
  • Auf x86_64 immer sicher, auf ARM64 mit LDUR/STUR

@big_endian

Markiert ein Struct-Feld als Big-Endian (Network Byte Order). Lese- und Schreibzugriffe auf das Feld erzeugen automatisch Byte-Swap-Instruktionen (bswap auf x86_64, rev auf ARM64).

type IpHeader = packed struct {
    @big_endian ethertype: int16;   // z. B. 0x0800 für IPv4
    @big_endian total_len: int16;
    @big_endian ident:     int16;
    @big_endian frag_off:  int16;
                ttl:       int8;
                protocol:  int8;
    @big_endian checksum:  int16;
};

Das Attribut steht direkt vor dem Feldnamen — ohne var und ohne Offset-Angabe. Für ein festes Speicherbild sorgt packed struct (→ Datentypen).

Seit lyxc 1.0.15G schreibt eine Feldzuweisung genau so viele Bytes, wie das Feld breit ist (mov [rax+off], sil / si / esi bei 1, 2 und 4 Byte) — Nachbarfelder bleiben unberührt, in gepackten wie in ungepackten Strukturen. Die Reihenfolge der Zuweisungen spielt damit keine Rolle mehr.

  • Nur sinnvoll für Felder ≥ 2 Bytes (int8 ist byteorder-neutral)
  • Der Swap greift bei jedem Zugriff, in beide Richtungen. Schreiben und anschließendes Lesen desselben Feldes liefert deshalb wieder den Ausgangswert — sichtbar wird die Wirkung erst am Rohspeicher: Steht dort die 1, liefert das @big_endian-Feld 16777216 (0x01000000).
 
@little_endian ist am Struct-Feld nicht schreibbar (#1864, gemeldet aus dieser Prüfung). Der Name steht zwar in der Attributliste des Parsers und im Hilfetext, wird an einer Feldposition aber nie angenommen:

type K = struct {
  @little_endian a: int32;    // Parse error at line 2: expected IDENT, got @ '@'
  b: int32;
}


Mit @big_endian an derselben Stelle übersetzt dasselbe Programm. Little-Endian ist auf x86-64 und ARM64 ohnehin die Speicherordnung — wer sie ausdrücklich hinschreiben will, kann es derzeit nicht. Frühere Fassungen dieser Seite führten beide Namen gleichrangig. Gemessen mit 1.1.14A.
 
Die frühere Schreibweise @at(0) @big_endian var feld: u16; ist in keinem Teil gültig. @at existiert nicht (unbekanntes Attribut), und die Kombination scheitert schon am Parser (expected IDENT, got @). Auch var gehört nicht in eine Struct-Deklaration.

@big_endian selbst arbeitet korrekt und braucht keine Offset-Angabe. (Issue #1247)

===== Ressourcen einbetten =====

Seit #1961 lassen sich Dateien ins Erzeugnis einbetten — das Gegenstück zu Delphis .res. Es gibt zwei Wege, und sie beantworten verschiedene Fragen.

==== @resource(„name“, „pfad“) ====

Auf Unit-Ebene. Trägt die Datei in eine benannte Tabelle ein, die zur Laufzeit durchsucht und aufgelistet werden kann:

unit main;
@resource("splash", "assets/splash.lyxi");

import std.res;

fn main(): int64 {
    var bild: pchar := ResFind("splash");
    var n:    int64 := ResLen("splash");
    return 0;
}

Der Pfad wird relativ zur Quelldatei aufgelöst. Fehlt die Datei, bricht die Übersetzung ab (error: @resource: Datei nicht lesbar: …) — eine fehlende Ressource fällt beim Bauen auf, nicht beim Kunden. Die Zugriffsfunktionen stehen in std.res.

<WRAP center round important 90%> @resource gibt es bisher nur mit --target=x86_64. Auf dem IR-Weg (lyxos, arm64, riscv, xtensa) bricht die Übersetzung mit einer Meldung ab, die auf #1968 verweist: die Tabelle besteht aus Zeigern in den Datenbereich, und dort gibt es bisher nur die nullterminierte Zeichenkettentabelle. </WRAP>

==== @embed(„pfad“) und @embed_len(„pfad“) ====

Im Ausdruck, aufgelöst zur Übersetzungszeit:

var s: pchar := @embed("assets/hilfe.txt");
var n: int64 := @embed_len("assets/hilfe.txt");

Der kürzere Weg, wenn genau eine Datei an genau einer Stelle gebraucht wird — dafür ohne Namen, ohne Auflisten und ohne Auswahl zur Laufzeit. Auf den IR-Zielen ist das der einzige Weg, und dort für Textdateien.

 
Warum ein Zeiger und kein [N]uint8-Feld: Lyx legt Felder mit acht Byte je Element ab, auch bei uint8. Ein eingebettetes 1-MB-Bild bräuchte damit 8 MB im Erzeugnis. Die Bytes liegen deshalb gepackt im Datensegment, und @embed liefert die Adresse. Gelesen wird mit peek8.

===== FFI-Attribute =====

Attribute für die Interoperabilität mit externen Bibliotheken und C-Code:

==== extern (Schlüsselwort, kein Attribut) ====

Externe Funktionen werden mit dem Schlüsselwort extern deklariert — ein Attribut @extern gibt es nicht.

@capabilities([system.exit, system.memory.heap, system.time])
import std.io;

extern fn time(t: int64): int64 link "libc.so.6";

fn main(): int64 {
    PrintLn(IntToStr(time(0)));
    return 0;
}

  • Kein Funktionskörper — nur Signatur
  • Die link-Klausel ist Pflicht und nennt die Bibliothek mit ihrem vollständigen Dateinamen („libc.so.6“, nicht „c“)
  • ABI: Standard-C-Aufrufkonvention (System V AMD64 auf Linux, AAPCS64 auf ARM64)
  • Fail-closed: Der Compiler kennt eine Zuordnung von FFI-Symbol zu Capability. Ohne passendes @capabilities wird die Deklaration abgewiesen (extern fn: unbekanntes FFI-Symbol erfordert @capabilities([…])), ebenso ein Symbol, das nicht in der Zuordnung steht.
 
Die link-Klausel ist seit lyxc 1.0.17C Pflicht (#1179). Fehlt sie, bricht schon der Parser ab: extern fn: die link-Klausel fehlt. Bis 1.0.16x übersetzte die Deklaration auch ohne, das Symbol wurde nie gebunden und der Aufruf lieferte still 0 — bei C-Funktionen ein plausibler Wert, der Fehler fiel damit kaum auf. Die eigene Standardbibliothek trug 33 solcher Deklarationen (std.time.Now() gab 0, env_get immer „“, abort() beendete nichts); alle sind ersetzt.

Ein falscher Bibliotheksname wird weiterhin nicht zur Übersetzungszeit gemeldet — den vollständigen Dateinamen angeben („libc.so.6“, nicht „c“) und einen neuen FFI-Aufruf einmal gegen einen bekannten Wert prüfen.

→ Details: FFI – C-Interop · Capabilities


==== @align(N) ====

Setzt das Allokations-Alignment einer Variablen in Bytes. Wirkt auf array- und heap-gestützte lokale Variablen; bei skalaren Stack-Variablen ohne Bedeutung.

fn main(): int64 {
    @align(64) var puffer: array<int64> := [1, 2];
    return puffer[0];
}


==== @volatile ====

Verhindert, dass der Compiler Lese- und Schreibzugriffe auf eine Variable wegoptimiert, zwischenspeichert oder umordnet. Essentiell für Memory-Mapped I/O und ISR-Kommunikation.

@volatile var uart_data_reg: int64 := 0x40011004;   // UART-Datenregister-Adresse
@volatile var isr_flag:      int64 := 0;             // Gesetzt von ISR, gelesen von Main

fn WaitForData(): void {
    while (isr_flag == 0) limit(1000000) { }   // Ohne @volatile würde der Compiler dieses Loop wegoptimieren
    isr_flag := 0;
}


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

Letzte Aktualisierung: 2026-09-05 (#1961, nachgemessen mit lyxc 1.2.2D) — Abschnitt „Ressourcen einbetten„ ergänzt: @resource (benannte Tabelle, nur x86-64) und @embed/@embed_len (zur Übersetzungszeit aufgelöst).

Vorherige letzte Aktualisierung: 2026-09-05 (#1908, gemessen mit lyxc 1.1.18A) — @integrity-Zeilen auf den gemessenen Stand gezogen: seit 1.1.15A nachgewiesen (kein „Erzeugnis unverändert“ mehr), Prüfstandszeile von den ungültigen Kurzformen memory_scrubbing/software_lockstep auf mode: scrubbed bzw. mode: software_lockstep berichtigt.

Vorherige letzte Aktualisierung: 2026-08-30 — alle 14 Attribute einzeln gegen lyxc 1.1.14A durchgemessen (siehe Abschnitt „Prüfstand„); struct-Größe im @packed-Beispiel berichtigt (6 statt 18, #1516 behoben); @little_endian am Feld als nicht schreibbar belegt und gemeldet (#1864).