Cross-Compilation mit Lyx

lyxc ist ein self-contained Cross-Compiler: kein externer Linker, keine separate Toolchain-Konfiguration. Eine einzige Binary erzeugt nativen Code für alle unterstützten Zielplattformen.

Guides · Compiler-Parameter


Alle Zielplattformen

Target-String Alias Architektur Format ABI Status
linux x86_64 x86_64 ELF64 SysV AMD64 Stabil
arm64 linux-arm64 ARM64 ELF64 AAPCS64 Stabil
linux-riscv64 RV64GC ELF64 LP64D Stabil
riscv riscv64 RV64GC ELF64 LP64D Stabil
arm-cm4 arm-cm33 ARM Cortex-M4/M33 ELF32 ARM EABI5 Stabil
arm_cm ARM Cortex-M (generisch) ELF32 ARM EABI5 Stabil
win64 windows-x86_64 x86_64 PE32+ Windows x64 Stabil
windows-arm64 ARM64 PE32+ Windows ARM64 Stabil
macosx64 macos-x86_64 x86_64 Mach-O SysV AMD64 Stabil
macos-arm64 ARM64 (Apple Silicon) Mach-O AAPCS64 Stabil
android android-arm64 ARM64 ELF64 (.so) Android Bionic Stabil
android-x86_64 x86_64 ELF64 (.so) Android Bionic Stabil
esp32 esp32s3 Xtensa LX6 ELF32 Vereinfacht SysV Experimentell

Standard: Kein –target → Host-Plattform (in der Regel linux/x86_64).


Typische Workflows

ARM64 Linux (Raspberry Pi, Embedded-Server)

lyxc main.lyx --target=arm64 -o firmware.elf

# Mit Energy-Optimierung für Batteriebetrieb:
lyxc main.lyx --target=arm64 --target-energy=1 -o sensor.elf

# Safety-Build (DO-178C):
lyxc flight.lyx --target=arm64 --lint --static-analysis -o flight.elf

Die erzeugte ELF verwendet /lib/ld-linux-aarch64.so.1 als Dynamic Linker — das muss auf dem Zielsystem vorhanden sein. Für Systeme ohne Standard-Loader:

 
--static gibt es in lyxc 1.0.16F nicht — der Aufruf bricht mit unbekannter Schalter und Exit 2 ab. Der Schalter sollte den Dynamic-Linker-Eintrag entfernen.

--no-libc-init wird angenommen, taucht aber in lyxc --help nicht auf: Der Aufruf läuft durch (Exit 0) und erzeugt ein lauffähiges Binary. Ob dabei der vorgesehene Bare-Metal-Stub statt des gewöhnlichen _start entsteht, ist von außen nicht abzulesen — für die Beispiele unten zum Cortex-M ist er weiterhin die dokumentierte Form.

Beides ist derzeit gegenstandslos: Der Compiler erzeugt ohnehin statische ELF-Dateien ohne Dynamic Linker — das meldet auch das LCBS-Audit jedes Builds (RELRO (kein GOT/PLT-Angriffsziel — statisches ELF)).

Windows x64 (Cross von Linux)

lyxc main.lyx --target=win64 -o app.exe
lyxc main.lyx --target=windows-arm64 -o app_arm64.exe

Erzeugt eine vollständige PE32+-Datei. Keine Wine- oder MSVC-Installation nötig.

Seit lyxc 1.1.3E sind aus einer solchen Binary auch Win32-Funktionen erreichbar: extern fn … link „kernel32.dll“ landet in der Import-Tabelle, mehrere DLLs nebeneinander sind zulässig, und Umgebungsvariablen (EnvLookupRaw, std.datetime, std.os) lesen den Windows-Umgebungsblock (#1671, #1674, #1676, #1677). Auch die frühere Größengrenze ist weg: eine Relokationstabelle über 0x1000 machte die Datei vorher unstartbar (#1672).


RISC-V (Embedded-Controller, HiFive, VisionFive)

# Generisches RISC-V RV64GC:
lyxc main.lyx --target=riscv -o controller.elf

# Linux-spezifisch (mit Linux ABI-Optimierungen):
lyxc main.lyx --target=linux-riscv64 -o app.elf

Einschränkung: Kein Dynamic Linking für RISC-V — immer statisch linken (–static) oder -l-Flags vermeiden.


ARM Cortex-M (Bare-Metal Microcontroller)

Für STM32, nRF52, RP2040 und kompatible Cortex-M4/M33-Systeme:

# Cortex-M4 / Cortex-M33:
lyxc main.lyx --target=arm-cm4 --no-libc-init -o firmware.elf

# Generisch Cortex-M (M0/M3):
lyxc main.lyx --target=arm_cm --no-libc-init -o firmware.elf

Wichtig: Kein Hardware-FPU-Support — Fließkomma läuft als Software-Emulation. Für DO-178C mit FPU: die betroffenen Funktionen mit @flight_crit annotieren — ein –no-fp-fold gibt es nicht.

Das ELF32-Format enthält e_flags = EABI5 | Thumb-interwork — direkt flashbar mit objcopy oder openocd.


ESP32 / ESP32-S3 (Xtensa LX6 — experimentell)

lyxc main.lyx --target=esp32 --no-libc-init -o app.elf
lyxc main.lyx --target=esp32s3 --no-libc-init -o app_s3.elf

Status: läuft, aber ohne Hardware-Nachweis. Die Defektkette, die hier früher als „Segfault bei komplexen Programmen„ stand, ist mit lyxc 1.1.9N behoben (#1782); Division und Modulo, die still 0 lieferten, mit 1.1.10G (#1789). Was bleibt:

  • Kein f64-Rechnen — Addition und Vergleich brechen beim Übersetzen ab (nachgemessen mit 1.1.11B)
  • Kein Dynamic Linking
  • Die Multiplikation ist nicht ausführbar geprüft — in keinem verfügbaren QEMU-Kern gibt es Mul32 (#1790). Das gilt sinngemäß für das ganze Ziel: geprüft ist, was der Compiler erzeugt, nicht was der Chip daraus macht.

Android (.so + JNI)

Android-Bibliotheken werden als .so erzeugt:

lyxc mylib.lyx --target=android --output-type=shared-lib -o libmylib.so

# Android API-Level explizit setzen (Default: 26 = Android 8.0, Minimum: 21):
lyxc mylib.lyx --target=android --android-api=29 --output-type=shared-lib -o libmylib.so

# Android x86_64 (Emulator):
lyxc mylib.lyx --target=android-x86_64 --output-type=shared-lib -o libmylib_x86.so

 
--android-api hat derzeit keine Wirkung (#1526). Alle Werte erzeugen dasselbe Programm — auch ein ungültiges wie 99, das ohne Beanstandung angenommen wird. Der Schalter ist also weder wirksam noch prüfend; das API-Level ist beim Einbinden auf Android-Seite sicherzustellen.

JNI-Funktionen werden mit @jni annotiert — der Compiler mangelt den Namen automatisch in das Java-Format Java_<class>_<method>:

import std.io;

@jni(class="com/example/MyApp", method="hello")
pub fn hello(): void {
  PrintLn("Hello from Lyx via JNI!");
}

@jni(class="com/example/MyApp", method="add")
pub fn add(a: int64, b: int64): int64 {
  return a + b;
}

Das erzeugte .so enthält ein .note.android.ident-Section mit dem API-Level — erforderlich für den Android-Loader.


Bare-Metal: kein Betriebssystem

Für Microcontroller und Custom-Bootloader ohne OS-Unterstützung:

lyxc main.lyx --target=arm-cm4 --no-libc-init --static -o bare.elf

Was –no-libc-init bewirkt:

  • Kein __libc_start_main-Aufruf
  • Kein DT_NEEDED libc.so.6 im ELF
  • 12-Byte BL main + exit-Stub statt 56-Byte Standard-_start
  • main() wird direkt als Einstiegspunkt verwendet

Compiler ist vollständig self-contained

lyxc benötigt keinen externen Linker (kein ld, lld, arm-none-eabi-ld). Der Compiler schreibt das fertige Binary (ELF64/ELF32/PE32+/Mach-O) direkt — Parsing, Codegen und Linking in einem Schritt.

Externe C-Bibliotheken werden trotzdem unterstützt:

# C-Bibliothek linken (lyxc löst Symbole selbst auf):
lyxc main.lyx -lm -lpthread -o app

# Eigene Shared Library einbinden:
lyxc main.lyx -L/opt/mylibs -lmylib -o app


Bekannte Einschränkungen

Plattform Einschränkung Workaround
ESP32 / Cortex-M Kein f64-Rechnen — Addition und Vergleich brechen beim Übersetzen ab Ganzzahl-Arithmetik, Festkomma
ARM Cortex-M alloc/new gibt es nicht — der Compiler weist beide ab (alloc/new gibt es auf einem freistehenden Ziel nicht) Statische Puffer, feste Feldgrößen
ESP32 Kein Dynamic Linking Immer statisch kompilieren
RISC-V Kein Dynamic Linking –static verwenden
ARM Cortex-M Kein Hardware-FPU Software-Float; für DO-178C: @flight_crit an der Funktion
ARM Cortex-M TrustZone nur Stub Nur M-Mode nutzen

Die frühere Zeile „ESP32: Segfault bei komplexen Programmen“ ist entfallen. Die Ursache war eine Defektkette im xtensa-Backend — Argumente, Param-Spill, Vorwärtsaufrufe, Feld-Offsets, Einstiegspunkt —, behoben mit lyxc 1.1.9N (#1782, #1765). Dazu kamen Division und Modulo, die vorher still 0 lieferten (#1789, 1.1.10G), sowie Zeichenkettenliterale und PC-relative Adressierung (#1786). Dieselbe Kette auf riscv war #1740 (1.1.9A) — dort waren es acht Defekte auf einmal, weil das Backend bis dahin nie ausgeführt geprüft worden war.

Was je Ziel übersetzt

Gemessen mit lyxc 1.1.11B: je ein Minimalprogramm pro Zeile, für jedes Ziel übersetzt. ✓ heißt „der Compiler erzeugt ein Binary„, nicht „auf dem Gerät nachgerechnet“ — für arm-cm4 und esp32 fehlt uns dafür die Hardware.

Was linux arm64 riscv arm-cm4 esp32
f64-Variable anlegen
f64-Addition bricht ab bricht ab
f64-Vergleich bricht ab bricht ab
Ganzzahl-Division
Multiplikation
Zeichenkettenliteral + PrintLn
Print(variable)
globale Variable lesen/schreiben
con-Konstante
alloc/free abgewiesen

Gegenüber dem Stand 1.0.21A hat sich das Bild grundlegend gedreht:

  • arm64 und riscv rechnen f64 vollständig. Der frühere Ausfall beim Vergleich (arm64, Opcode 42) und bei der Addition (riscv, Opcode 20) ist weg.
  • con-Konstanten waren auf allen IR-Zielen 0 — die Vorab-Erfassung der Modul-Globalen kannte nur NK_VAR_DECL, nicht NK_CON_DECL (#1751, 1.1.8A). Ein reiner Übersetzungstest wäre grün gewesen; der Fehler saß im erzeugten Code. Nachgezogen: f64-Konstanten (#1801), ~0 im Anfangswert (#1799) und negative f64 (#1803).
  • import std.io trägt jetzt auf allen IR-Zielen. Die frühere Aussage „scheitert für alle IR-Ziele, dort fehlen 194 Builtins„ (#1388) gilt nicht mehr.
  • Virtueller und Interface-Dispatch laufen auf dem IR-Pfad über einen type-id-Switch statt über eine VMT (#1773, 1.1.9G) — kein eigenes Layout, keine Interface-Tabellen.
 
Die Backends weisen laut ab, statt still falschen Code zu erzeugen (#1339):

> error: arm-cm4-Backend kennt Opcode 20 nicht — er wuerde stillschweigend verschwinden (#1339).
> error: arm-cm: alloc/new gibt es auf einem freistehenden Ziel nicht.
> 


Das ist die richtige Reihenfolge: ein Abbruch beim Übersetzen ist einer Rechnung vorzuziehen, die auf dem Zielgerät stillschweigend nichts tut.

Der Wächter hat allerdings eine Lücke, die #1789 offengelegt hat: auf esp32 standen IRO_DIV und IRO_MOD als „behandelt“ in der Liste — behandelt wurden sie auch, nur mit einem Rumpf, der still 0 zurückgab. Eine Liste, die Vorhandensein prüft, sagt nichts über Richtigkeit. Dafür braucht es Ausführung.

Letzte Aktualisierung: 2026-08-27 — Einschränkungstabelle und Ziel-Matrix gegen lyxc 1.1.11B neu erhoben (je ein Minimalprogramm pro Zeile und Ziel übersetzt): #1740, #1751, #1765, #1782, #1786, #1789, #1799, #1801, #1803 behoben, #1388 hinfällig.

Vorherige Aktualisierung: 2026-08-19 — Windows-Abschnitt um DLL-Aufrufe, Umgebungsvariablen und die Relokationsgrenze ergänzt (#1671, #1672, #1674, #1676, #1677), unter Wine mit lyxc 1.1.3I nachgemessen.

Vorherige Aktualisierung: 2026-08-15 — Gleitkomma-Matrix gegen 1.0.20A gemessen (#1339)