Eskiueskiu v0.8.0

Cross-Compiling

eskiuc emits objects for a target other than the build host. Set the triple with --target; three companion flags tune the machine and ABI:

Flag Purpose
--target Target triple (e.g. x86_64-linux-gnu, armv6k-none-eabihf). Empty = host.
--mcpu Target CPU passed to LLVM (e.g. mpcore). Empty = generic when cross, host CPU when native.
--mattr LLVM feature string, -mattr syntax (e.g. +vfp2).
--reloc Relocation model: pic (default), static, or dynamic-no-pic.

The registered backends are AArch64, x86-64, and 32-bit ARM. eskiuc only produces the object file; link it with a cross toolchain that targets the same platform.

Platform macros

The preprocessor predefines a platform macro so imported stdlib code can branch per OS:

--freestanding additionally predefines __ESKIU_FREESTANDING__ and routes alloc/free to caller-supplied esk_alloc/esk_free instead of libc.

Hard-float ARM

A triple ending in hf (e.g. armv6k-none-eabihf) selects the hard-float ABI. LLVM then stamps the Tag_ABI_VFP_args build attribute on the object, which a hard-float linker requires to mix the object with hard-float libraries. Float arguments pass in VFP registers (s0, s1, ...) rather than core registers.

Verify it on an emitted object:

llvm-readobj -A out.o | grep -A1 'VFP_args'
#   TagName: ABI_VFP_args
#   Description: AAPCS VFP

Example: Nintendo 3DS

The 3DS is an ARM11 MPCore (armv6k) with VFPv2, hard-float, and a homebrew loader that applies static relocations. Compile an Eskiu source to a 3DS object:

eskiuc lib.esk -o lib.o \
    --target armv6k-none-eabihf --mcpu mpcore --mattr +vfp2 \
    --reloc static --freestanding

Then link it with the devkitARM toolchain, alongside a C entry point and libctru, and package the result:

# C side (entry point + libctru calls), same arch flags:
arm-none-eabi-gcc -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft \
    -I$DEVKITPRO/libctru/include -c main.c -o main.o

# Link the C object + the Eskiu object into a 3DS ELF, then wrap it as .3dsx:
arm-none-eabi-gcc -specs=3dsx.specs -march=armv6k -mtune=mpcore -mfloat-abi=hard \
    main.o lib.o -L$DEVKITPRO/libctru/lib -lctru -lm -o app.elf
3dsxtool app.elf app.3dsx

The link succeeds only when both objects agree on the float ABI, which is why the hf triple (and the resulting Tag_ABI_VFP_args) matters: without it the linker rejects the Eskiu object against hard-float libctru.

Eskiu declarations compile to C-ABI symbols, so a main.c can call an Eskiu function by declaring it extern. The self-hosted compiler does not yet forward --mcpu/--mattr/ --reloc, so 3DS builds use the reference eskiuc.

Example: Windows (x86-64)

The x86 backend emits COFF objects for a Windows triple with no extra flags; LLVM lowers the Microsoft x64 calling convention (first integer arguments in rcx/rdx/r8/r9) from the triple:

eskiuc app.esk -o app.obj --target x86_64-pc-windows-gnu --freestanding
file app.obj    # Intel amd64 COFF object file

Link with a Windows linker. LLVM's lld-link needs an import library for any DLL the program calls; generate one from a .def with llvm-dlltool (both ship with LLVM), so no Windows SDK is required to link:

printf 'LIBRARY kernel32.dll\nEXPORTS\nExitProcess\nGetStdHandle\nWriteFile\n' > kernel32.def
llvm-dlltool -m i386:x86-64 -d kernel32.def -l kernel32.lib
lld-link /entry:mainCRTStartup /subsystem:console app.obj kernel32.lib /out:app.exe

A freestanding program provides its own entry point (/entry:) and calls Win32 directly. A hosted program instead links the MinGW or MSVC C runtime and uses the ordinary main. The resulting .exe runs on Windows, or under wine for local testing.

Exceptions (try/throw/catch) work on a mingw target: the compiler emits the SEH EH personality (__gxx_personality_seh0) there instead of the Itanium __gxx_personality_v0, so linking with a mingw g++ (which supplies the __cxa_* runtime and the SEH unwinder) produces a working .exe. This is exercised end to end on a native Windows runner in .github/workflows/windows.yml.

The OS-level stdlib modules carry Windows backends: <sysheap> maps pages with VirtualAlloc/VirtualFree (no mmap), <time> uses the Win32 clocks (GetTickCount64, GetSystemTimeAsFileTime, Sleep), <threading> links against winpthreads, and <net> (blocking TCP: listen/accept/connect/send/recv) uses the Winsock backend (WSAStartup + closesocket, link -lws2_32). A blocking, thread-per-connection server works on Windows with these. Each has an end-to-end check on a native Windows runner in .github/workflows/windows.yml.

The async stack runs on Windows too. <eventloop> swaps its epoll/kqueue reactor for WSAPoll over a user-space pollfd set (rebuilt each wait, since WSAPoll is stateless), and <net_async> uses ioctlsocket(FIONBIO) + recv/send + WSAGetLastError in place of fcntl(O_NONBLOCK) + read/write + errno. One Windows subtlety: a SOCKET is not a small sequential fd (handles like 236 are normal), so the loop's fd-indexed slot table grows to fit the largest handle rather than assuming a dense [0, max_fds) range. A non-blocking socket round-trip over the reactor is checked end to end on the native Windows runner.