Un compilador para todas las plataformas
eskiuc compila para la máquina en la que corre y, con --target,
para otra: la web, Android, iOS, Windows o una tarjeta sin sistema operativo.
El compilador trae integrados los backends de AArch64, x86 (x86-64 e i686 de 32 bits),
ARM de 32 bits y WebAssembly (wasm32). La extensión del nombre que le pasas a
-o decide qué se genera:
| Salida | Qué obtienes |
|---|---|
app, app.exe | Un ejecutable, enlazado con el driver de C de la plataforma |
.o, .obj | Un archivo objeto, para que lo enlaces tú |
.a, .lib | Una biblioteca estática (el objeto dentro de un archivo, que escribe el propio eskiuc) |
.so, .dylib, .dll | Una biblioteca compartida, enlazada con -shared |
Con una biblioteca metes un módulo de Eskiu en una app que se enlaza con otro sistema de build, como un proyecto de Xcode, un módulo de Gradle o un build de emscripten. Las funciones de Eskiu son símbolos de C, así que el código C, C++, Objective-C o JNI de la app las declara y las llama.
macOS, Linux y Windows
Hay builds del compilador para cuatro sistemas. En macOS y Linux, el script de instalación elige el correcto:
curl -fsSL https://eskiu-lang.org/install.sh | sh
Sin --target, el programa se compila para la máquina donde estás:
eskiuc run app.esk # compila, ejecuta y limpia
eskiuc app.esk -o app # conserva el ejecutable
eskiuc app.esk -o libapp.a # o genera una biblioteca estática
Si no pasas --mcpu, el CPU es el base de la plataforma, como en clang:
apple-m1 en Apple arm64 y generic en lo demás. Así, un binario
compilado en una máquina corre en otra de la misma plataforma.
Windows se valida de punta a punta en un runner nativo de CI (excepciones y las pilas de red bloqueante y asíncrona), pero todavía no forma parte del release gate.
WebAssembly
--target wasm32-unknown-emscripten compila para
emscripten, la herramienta que usa la mayoría de los
proyectos web. Si no hay $CC, eskiuc enlaza con
emcc, así que la extensión de la salida decide qué escribe emcc
(.js con su .wasm, .html o .wasm), y
--link-arg le pasa opciones a emcc:
eskiuc app.esk --target wasm32-unknown-emscripten -o app.js --link-arg=-sEXIT_RUNTIME=1
node app.js
Dentro de un proyecto de emscripten que ya tienes
Genera una biblioteca estática y enlázala como cualquier otra:
eskiuc tracker.esk --target wasm32-unknown-emscripten -o libtracker.a
emcc main.cpp libtracker.a ...
Las funciones de Eskiu son símbolos de C, así que en C++ (en un binding de embind, por
ejemplo) se declaran extern "C" y se llaman directo:
// main.cpp
extern "C" int tracker_score(int hits, int misses); // definida en tracker.esk
Desde JavaScript llegas a una función que exportas con
-sEXPORTED_FUNCTIONS=_nombre de emcc (para tracker_score,
-sEXPORTED_FUNCTIONS=_tracker_score).
Qué funciona ahí
La biblioteca estándar funciona igual que en otros sistemas POSIX, con un event loop
sobre poll(), así que los futures, los timers y los canales corren en node.
Las excepciones funcionan en emscripten con su runtime basado en JavaScript: un programa
que lanza o atrapa excepciones se enlaza con -sDISABLE_EXCEPTION_CATCHING=0.
Los hilos necesitan el build -pthread de emscripten.
WASI y otros triples de wasm32
wasm32-wasip1 y wasm32-unknown-unknown se enlazan con el propio
clang --target=... de LLVM. WASI necesita un sysroot
(--link-arg=--sysroot=...) y wasm32-unknown-unknown normalmente
se compila con --freestanding. En estos triples, try y
throw son un error de compilación.
Android
En Android el triple lleva el nivel de API: aarch64-linux-android24,
armv7a-linux-androideabi24 o x86_64-linux-android24. Si no hay
$CC, eskiuc enlaza con el clang del NDK, que busca en
$ANDROID_NDK_HOME, en $ANDROID_NDK_ROOT o en el NDK más nuevo
del SDK ($ANDROID_HOME/ndk, o el directorio que usa por defecto el SDK
manager).
Una app carga una biblioteca compartida:
eskiuc tracker.esk --target aarch64-linux-android24 -o libtracker.so
La app la carga con System.loadLibrary("tracker"), y su código JNI, o la
parte nativa de un módulo de React Native, declara las funciones de Eskiu y las llama
como funciones de C. pthread viene en la libc de Android, así que un programa con hilos
no enlaza -lpthread, y uno que lanza excepciones enlaza
libc++_static y libc++abi del NDK.
iOS
arm64-apple-ios15.0 compila para dispositivos, y
arm64-apple-ios15.0-simulator (o x86_64-apple-ios15.0-simulator)
para el simulador. Si no hay $CC, eskiuc enlaza con
xcrun -sdk iphoneos clang (iphonesimulator para un triple de
simulador). Una app incluye una biblioteca estática por cada slice, y
xcodebuild -create-xcframework las junta:
eskiuc tracker.esk --target arm64-apple-ios15.0 -o ios/libtracker.a
eskiuc tracker.esk --target arm64-apple-ios15.0-simulator -o sim/libtracker.a
xcodebuild -create-xcframework -library ios/libtracker.a -library sim/libtracker.a \
-output Tracker.xcframework
La ABI de C es la de Darwin AArch64, igual que en macOS.
Programas de Windows con ventana
Un programa que abre su propia ventana, como uno que usa el paquete gpu, se enlaza para el subsistema gráfico, y así Windows no le abre una consola al lado:
eskiuc viewer.esk -o viewer.exe --subsystem=windows
El punto de entrada sigue siendo main: el driver le pasa
-mwindows a un enlace de mingw, y /subsystem:windows con
/entry:mainCRTStartup a uno de MSVC. En otras plataformas la opción no hace
nada. Para generar objetos de Windows desde otro sistema
(--target x86_64-pc-windows-gnu, enlazados con lld-link), revisa
Cross-compiling.
Bare metal
Un triple cuyo sistema operativo es none apunta a hardware sin sistema
operativo. Ese triple no define ninguna macro de sistema (__linux__,
__APPLE__, _WIN32), así que el código portable tiene que
contemplar ese caso por su cuenta. --freestanding predefine
__ESKIU_FREESTANDING__ y manda alloc y free a un
esk_alloc y un esk_free que escribes tú, en lugar de usar libc:
eskiuc kernel.esk --target aarch64-unknown-none-elf --freestanding -o kernel.o
El kernel de ARM64 del repositorio se compila así y arranca en QEMU; el capítulo 18 del
libro lo explica paso a paso.
--mcpu, --mattr y --reloc (pic,
static o dynamic-no-pic) ajustan la máquina y el modelo de
relocalización.
Nintendo 3DS
La 3DS tiene un ARM11 MPCore (armv6k) con VFPv2, hard-float, y un cargador
de homebrew que aplica relocalizaciones estáticas:
eskiuc lib.esk -o lib.o \
--target armv6k-none-eabihf --mcpu mpcore --mattr +vfp2 \
--reloc static --freestanding
Después lo enlazas con el toolchain de devkitARM, junto con un punto de entrada en C y libctru, y empaquetas el resultado:
arm-none-eabi-gcc -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft \
-I$DEVKITPRO/libctru/include -c main.c -o main.o
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
El sufijo hf elige la ABI hard-float, que el linker necesita para juntar el
objeto de Eskiu con libctru, que es hard-float. El
caso de estudio de la 3DS muestra lo que se hizo así:
un demo de realidad aumentada con cámara y giroscopio, probado en la consola.
Tipos de C cuyo ancho depende de la plataforma
En C, size_t, ssize_t y long miden 32 bits en
wasm32, ARM de 32 bits e i686, y long también mide 32 bits en Windows. Eskiu
ve cada uno como un int64. Si los escribes como size_t,
ssize_t o c_long (de import <ctypes>;) en los
parámetros o el resultado de un extern, la declaración usa el tipo de C de la
plataforma, el argumento se convierte a ese tipo y el resultado se vuelve a ensanchar:
import <ctypes>;
extern size_t strlen(string s); // correcto en 32 y en 64 bits
La biblioteca estándar declara así las funciones de C que usa, por lo que
strlen, memcpy, malloc, read o
snprintf quedan bien en cualquier plataforma. Si tu programa declara una de
estas funciones por su cuenta, debería hacer lo mismo: un int64 ahí está mal
en una plataforma de 32 bits, y en WebAssembly la llamada truena por la firma que no
coincide.
La referencia completa, con las macros de cada plataforma y el enlace paso a paso en Windows, está en Cross-compiling, en la documentación (en inglés).
eskiu