Documentation pages
Targets
sultanc targets lists the 38 registered compilation targets. This page records, for each one,
which backend generates its code, what standard library support it has, how its executable is
linked and how it is verified. It also describes where target-specific code lives in the
repository, for contributors who add or port a target.
Backends
SultanC has two code-generation backends, selected with --backend=native or --backend=llvm.
The native backend is SultanC's own code generator and, with the built-in linkers, needs no
external toolchain. It is available for the x86_64 and aarch64 targets of Linux, Android, FreeBSD,
macOS and Windows (x86_64-linux, aarch64-linux-gnu, x86_64-android, aarch64-android,
x86_64-freebsd, aarch64-freebsd, arm64-darwin, x86_64-darwin, x86_64-windows-msvc,
aarch64-windows-msvc) and for wasm32-wasi and wasm32-browser. Requesting it for any other
target reports SULTANC-BACKEND-UNAVAILABLE.
The LLVM backend is available for every target and requires a compiler built with LLVM
(./build.sh --enable-llvm). It produces objects for every target. Targets without an object
format (NVPTX, XCore) are compiled with --emit=asm, which works for every target. The status of
each target under the LLVM backend, with what was run and what was only built, is kept in
LLVM targets progress.
The الدعم (support) column of sultanc targets carries one of three values: مضيف ذاتي
(self-hosted, arm64-darwin and x86_64-linux), ترجمة متقاطعة (cross-compilation, every other
target) and تجريبي (experimental, none today). The compiler also compiles itself on the Android
and FreeBSD x86_64 and aarch64 targets; Self-hosting on Android and
FreeBSD records how that was verified.
Target matrix
Operating-system targets
These targets have the full standard library, including files, processes, sockets and threads (except where noted). All of them use raw kernel calls or the platform's system interface and link no libc; Platform dependencies lists every import.
| Target | Architecture | Backend | Executable | Verified run |
|---|---|---|---|---|
x86_64-linux |
x86_64 | native, LLVM | built-in ELF linker | native |
aarch64-linux-gnu |
aarch64 | native, LLVM | built-in ELF linker | qemu-user |
riscv64-linux-gnu |
riscv64 | LLVM | built-in ELF linker | qemu-user |
loongarch64-linux-gnu |
loongarch64 | LLVM | built-in ELF linker | qemu-user |
powerpc64le-linux-gnu |
powerpc64le | LLVM | built-in ELF linker | qemu-user |
s390x-linux-gnu |
s390x | LLVM | built-in ELF linker | qemu-user |
mips64el-linux-gnuabi64 |
mips64el (n64) | LLVM | built-in ELF linker | qemu-user |
sparcv9-linux-gnu |
sparcv9 | LLVM | built-in ELF linker | qemu-user |
i686-linux-gnu |
x86 | LLVM | built-in ELF linker | qemu-user |
armv7-linux-gnueabihf |
arm | LLVM | built-in ELF linker | qemu-user |
x86_64-android |
x86_64 | native, LLVM | built-in ELF linker | native (static executable on the Linux kernel) |
aarch64-android |
aarch64 | native, LLVM | built-in ELF linker | qemu-user |
armv7-android |
arm | LLVM | built-in ELF linker | qemu-user |
arm64-darwin |
aarch64 | native, LLVM | built-in Mach-O linker | natively on arm64 (native backend) |
x86_64-darwin |
x86_64 | native, LLVM | built-in Mach-O linker | built |
x86_64-windows-msvc |
x86_64 | native, LLVM | built-in COFF linker | Wine |
aarch64-windows-msvc |
aarch64 | native, LLVM | built-in COFF linker | built |
x86_64-freebsd |
x86_64 | native, LLVM | built-in ELF linker | FreeBSD 14.5 VM under qemu |
aarch64-freebsd |
aarch64 | native, LLVM | built-in ELF linker | FreeBSD 14.5 VM under qemu |
riscv64-freebsd |
riscv64 | LLVM | built-in ELF linker | FreeBSD 14.5 VM under qemu |
Linux and Android targets use the Linux kernel ABI directly. The 32-bit targets (i686-linux-gnu,
armv7-linux-gnueabihf, armv7-android) use the time64 and large-file system calls. On them, and
on every other target that lacks a divide instruction or a wide divide, the backend expands
division in IR so no compiler-rt is linked (see What lives where).
The FreeBSD targets use raw system calls with no libc. They require FreeBSD 13 or later (for
close_range) and are built against the 14.2 headers. The executable is static, with
ELFOSABI_FREEBSD and the .note.tag ABI note, which the built-in linker keeps and covers with a
PT_NOTE header.
Linking
The compiler chooses the linker; there is no option to pick one.
| Output | Linker |
|---|---|
| ELF executables of x86_64 and aarch64 (Linux, Android, FreeBSD), riscv64 (Linux, FreeBSD), loongarch64, i686, armv7 (Linux, Android), powerpc64le, s390x, mips64el and sparcv9 Linux | SultanC's ELF linker (compiler/link/elf): ELF64 and ELF32, little- and big-endian images. riscv64, loongarch64 and the 32-bit and big-endian targets get static executables only, which is what SultanC programs are. |
| Mach-O, COFF and WebAssembly executables | SultanC's Mach-O, COFF and wasm linkers |
| freestanding and accelerator targets | none: objects or assembly (Cortex-M images are linked outside the compiler, see Cortex-M) |
A hosted ELF target the ELF linker does not lay out would be linked by ld.lld -static -e _start
(compiler/driver/external_linker.sn) only when its target policy names ld.lld; none does now.
ld.lld is found in this order: $SULTANC_LINKER; bin/ld.lld of the LLVM installation whose
libLLVM the compiler is linked with (the directory next to its lib/); ld.lld on PATH.
A link input the target's linker does not read (no object, archive, library or linker script) is
reported as SULTANC-LINK-INPUT-UNSUPPORTED with its path. For macOS and iOS a text-based dylib
stub of an Apple SDK (.tbd, as MacOSX.sdk/usr/lib/libSystem.tbd) stands for the dylib its
install name names, as with ld64: the image loads that dylib and binds the symbols the stub
exports to it.
WebAssembly
| Target | Backend | Executable | Notes |
|---|---|---|---|
wasm32-wasi |
native, LLVM | built-in wasm linker | the full std over WASI preview 1; sockets are limited to preopened ones; no processes, threads or file locks. Runs under a wasmtime-compatible runner. |
wasm32-browser |
native, LLVM | built-in wasm linker | freestanding: language core only. The module has no imports; its _start returns main's value, and node runs it. |
wasm64-unknown-unknown |
LLVM | objects | freestanding: language core only. The wasm linker lays out 32-bit memories only. |
Freestanding and accelerator targets
These targets have no OS layer; programs use the language core and std modules that do not
depend on an operating system.
| Target | Architecture | Output |
|---|---|---|
riscv32-unknown-elf |
riscv32 | objects, assembly |
avr-unknown-unknown |
avr | objects, assembly |
msp430-unknown-elf |
msp430 | objects, assembly |
hexagon-unknown-elf |
hexagon | objects, assembly |
lanai-unknown-unknown |
lanai | objects, assembly |
bpfel-unknown-none |
bpf | objects, assembly |
amdgcn-amd-amdhsa |
amdgpu | objects, assembly |
spirv64-unknown-unknown |
spirv64 | SPIR-V objects, assembly |
nvptx64-nvidia-cuda |
nvptx64 | assembly only |
xcore-unknown-unknown |
xcore | assembly only |
thumbv6m-none-eabi |
arm (ARMv6-M) | objects, assembly; Cortex-M images |
thumbv7m-none-eabi |
arm (ARMv7-M) | objects, assembly; Cortex-M images |
thumbv7em-none-eabi |
arm (ARMv7E-M) | objects, assembly; Cortex-M images |
thumbv7em-none-eabihf |
arm (ARMv7E-M, FPU) | objects, assembly; Cortex-M images |
These targets cannot be linked into an executable by SultanC: requesting exe reports
SULTANC-LINK-OUTPUT-UNSUPPORTED. Use -c or --emit=obj for objects and --emit=asm for
assembly. tests/en/target/language_core.sn (records, tagged unions, generics, and integer
arithmetic of every width) compiles for each of them. A program that uses an OS service (printing,
files) is rejected for them at check with SULTANC-TARGET-FEATURE-UNSUPPORTED; the compiler
itself is such a program, so sultanc check compiler/main.sn passes for the 21 hosted targets
only. Function values are closures whose environment the runtime allocates, and these targets
have no allocator, so a program that takes a function value is rejected for them
(SULTANC-BACKEND-UNSUPPORTED-CONSTRUCT). BPF before ISA v4 has no signed division; signed
division divides the magnitudes there. eBPF has no floating point, so LLVM rejects float
arithmetic for it.
Cortex-M targets
The four Arm M-profile targets are bare-metal ELF targets of the LLVM backend (the native backend
has no 32-bit Arm code generator). Their target conditions are الهدف.المعمارية == "arm" with
الهدف.المنصة == "freestanding", and environment eabi or eabihf.
| Target | Cores | Processor baseline | Floating point | Divide | std atomics | QEMU boards |
|---|---|---|---|---|---|---|
thumbv6m-none-eabi |
Cortex-M0, M0+, M1 | cortex-m0, +strict-align,+atomics-32 |
none | expanded in IR | 32-bit load, store, fence | microbit |
thumbv7m-none-eabi |
Cortex-M3 | cortex-m3 |
none | SDIV/UDIV | 32-bit load, store, fence | lm3s6965evb, mps2-an385 |
thumbv7em-none-eabi |
Cortex-M4, M7 | cortex-m4 (DSP), FPU off |
none | SDIV/UDIV | 32-bit load, store, fence | mps2-an386, mps2-an500 |
thumbv7em-none-eabihf |
Cortex-M4F, M7 | cortex-m4, +vfp4d16sp (FPv4-SP-D16) |
f32 in hardware | SDIV/UDIV | 32-bit load, store, fence | mps2-an386, mps2-an500 |
The facts live in compiler/backend/target/target_registry.sn (the M-profile class is the
target's processor, target_model.sn صنف_ذراع_إم) and are passed to LLVM as CPU and feature
strings (أساس_توليد_ذراع_إم).
- ABI: AAPCS, little-endian ELF32 relocatable objects with Arm build attributes (CPU name,
architecture profile
M,Tag_ABI_align_preserved: the stack is 8-byte aligned at every public interface).eabipasses floats in core registers;eabihfuses AAPCS-VFP (s0-s15,d0-d7), which LLVM selects from the environment. - No run-time library. SultanC links neither libgcc nor compiler-rt, so no object for these
targets references an
__aeabi_*routine. 64-bit division is expanded in IR as on every 32-bit target. On ARMv6-M, which has no divide instruction, no 32 x 32 -> 64 multiply and no 64-bit shift, 32-bit division, 64-bit multiplication and 64-bit shifts by a variable amount are expanded too (compiler/backend/provider/llvm/freestanding_routines.sn) instead of calling__aeabi_uidiv,__aeabi_lmul,__aeabi_llsl,__aeabi_llsrand__aeabi_lasr. - Floating point. A float operation the FPU cannot do would call a soft-float routine
(
__aeabi_fadd,__aeabi_dmul,__aeabi_f2d, ...). It is rejected instead, withSULTANC-TARGET-FEATURE-UNSUPPORTEDat the source statement that holds the operation, naming the operation, its type and the source function: every float arithmetic, comparison or conversion on theeabitargets, every f64 one onthumbv7em-none-eabihf, andfremeverywhere. Loading, storing and moving floats, negation,absandcopysignare allowed. A Cortex-M7 runsthumbv7em-none-eabihfcode; its double-precision FPv5-D16 needs a processor selection the registry does not offer yet. - Atomics. ARMv6-M has no exclusive accesses (LDREX/STREX); ARMv7-M has them up to 32 bits only.
On these targets
std.sync.atomicdeclares the 32-bit atomic loads and stores and the fences (atomic_u32,atomic_u32_new,atomic_u32_load,atomic_u32_store,atomic_fence), which are LDR/STR with DMB. The 64-bit andatomic_usizeforms and every read-modify-write operation are declared for other targets only (SULTANC-TARGET-FEATURE-UNSUPPORTEDfor the Arabic names; the English aliases reportSULTANC-NAME-UNKNOWN). The target conditions the bootstrap compilers parse cannot tell ARMv6-M from ARMv7-M, so the 32-bit read-modify-write atomics of ARMv7-M are not offered yet. Any atomic wider than the target does inline is rejected by the backend. - Like every freestanding target, these have no allocator: strings, vectors and closures are rejected.
Start-up code and linking
The compiler emits the start-up into the object of every Cortex-M program with an entry, as it emits
_start for the hosted targets (compiler/backend/provider/llvm/cortex_m_entry.sn), so no
assembler or C toolchain is involved:
.vector_table: the initial stack pointer__stack_top,Reset_Handlerand the 14 system exceptions, each a weak alias of a spinningDefault_Handler;Reset_Handlercopies.datafrom FLASH to RAM, zeroes.bss, enables the FPU onthumbv7em-none-eabihf(CPACR CP10/CP11), runs.init_array, callsmainand passes the low word of its result to__sultanc_exit, whose weak default stops the core in aWFIloop;__sultanc_semihosting_exit(Arm semihostingSYS_EXIT_EXTENDED) and__sultanc_semihosting_fault(exit status 132; a SultanC trap isUDF, which escalates to a HardFault), for runs under QEMU or a debugger.
ARMv6-M instructions only, so one sequence serves every class. The linker scripts in
library/std/os/platform/none/arm/cortex_m are data files the linker is given explicitly:
| File | Contents |
|---|---|
cortex_m.ld |
the section layout: vector table at the start of FLASH, .text, .rodata, .init_array, .ARM.extab/.ARM.exidx in FLASH, .data in RAM loaded from FLASH, .bss in RAM, __stack_top at the end of RAM, and the bounds the reset handler reads |
board_mps2.ld, board_lm3s6965.ld, board_nrf51.ld |
the FLASH and RAM regions of the QEMU boards, then INCLUDE cortex_m.ld |
semihosting.ld |
__sultanc_exit = __sultanc_semihosting_exit; and HardFault_Handler = __sultanc_semihosting_fault;, for QEMU and debugger runs only: on hardware with no debugger, BKPT faults |
SultanC does not link these targets: requesting exe reports SULTANC-LINK-OUTPUT-UNSUPPORTED.
The built-in ELF linker has no Arm relocations or linker-script support yet. Link an image with
ld.lld, for example:
./build/sultanc --backend=llvm --target=thumbv7m-none-eabi --emit=obj program.sn -o program.o
D=library/std/os/platform/none/arm/cortex_m
ld.lld -T $D/board_mps2.ld -L $D --gc-sections program.o $D/semihosting.ld -o program.elf
qemu-system-arm -M mps2-an385 -nographic -monitor none -serial none \
-semihosting-config enable=on,target=native -kernel program.elf; echo $?
tools/tests/cortex_m_tests.sh does this for every test in tests/{en,ar}/target that names a
Cortex-M target and runs each image on the target's QEMU boards.
Not yet supported: ve-linux-gnu
ve-linux-gnu (NEC Vector Engine) compiles to objects and assembly but has no std OS layer. VE has
no kernel trap: a system call is a request to the VEOS daemon. The number and arguments are stored
in a per-thread shared-memory area whose address the VEOS and glibc start-up places in the thread
control block (tcbhead_t.shm_offset, glibc-ve sysdeps/unix/sysv/linux/ve/sysdep.h), and then
monc is issued. Porting needs that start-up protocol and VE hardware or an emulator to verify it.
What lives where
| Layer | Owner | Notes |
|---|---|---|
| Kernel ABI (syscall numbers, flags, record layouts, byte order) | std: library/std/os/platform/** |
Linux tables are generated from each architecture's kernel UAPI headers (tools/generate/generate_linux_syscalls.py, generate_linux_uapi.py). The 32-bit ABIs take the ilp32: rows of tools/generate/registry/linux_syscalls.def (clock_gettime64, ppoll_time64, mmap2, _llseek, fcntl64). FreeBSD's are generated from its headers (releng/14.2) into platform/unix/freebsd: one system-call table for every architecture (generate_freebsd_syscalls.py, registry/freebsd_syscalls.def, each row with its syscalls.master prototype), and the flag values, struct stat offsets and errno classification (generate_freebsd_uapi.py, read with clang -E -dM and offsetof). |
| OS providers (files, processes, sockets, memory, clocks) | std | One guarded branch per OS in library/std/os/*.sn. The direct-kernel provider in platform/unix/linux also serves FreeBSD: it takes its numbers from the selected table (syscalls/selection.sn) and its constants from the selected kernel's values (linux/uapi.sn or freebsd/uapi.sn, same function names). Where FreeBSD's call differs, a FreeBSD block handles it: fstatat for statx, getdirentries records, __getcwd, wait4 for waitid, fork for clone, getrlimit/setrlimit for prlimit64, and the __sysctl hw.pagesize for the procfs auxv. |
Kernel entry instruction, _start, error-flag normalization |
backend (compiler/backend/provider/llvm/{lowering,entry}.sn) |
The only per-architecture code outside std. i386 passes the number and sixth argument through a stack block so EBP can be saved. FreeBSD flags errors with the carry flag (amd64, arm64) or t0 (riscv) and a positive errno, which the sequence turns into -errno. Its _start takes the argument block from rdi, x0 or a0, and the executable carries the .note.tag ABI note (osrel 1402000) besides ELFOSABI_FREEBSD. |
| Arithmetic the target lacks | backend (compiler/backend/provider/llvm/wide_division.sn; checked multiply in lowering.sn) |
Expanded in IR so no compiler-rt is linked. 64-bit division on 32- and 16-bit targets is expanded, and so is division of every width on targets with no divide instruction (ARMv7-A, which does not guarantee SDIV/UDIV; AVR; MSP430; Hexagon; Lanai), by one restoring shift-and-subtract function per width (sultanc.udivmodN). SPARC V9 divides 8-, 16- and 32-bit operands in 64 bits (SDIVX/UDIVX) instead of the deprecated Y-register SDIV/UDIV, because QEMU 8.2's sdiv also divides by the zero-extended divisor and returns wrong narrow signed quotients. Where there is no 128-bit product (WebAssembly, SPARC V9, the 32-bit targets), a checked 64-bit multiply is built from 32-bit halves, and every llvm.*mul.with.overflow.i64 the optimizer forms (InstCombine turns b > MAX / a into one) is replaced by the same expansion after the pipeline, since LLVM would call __multi3. BPF divides signed values through their magnitudes. |
| Address spaces | backend (compiler/backend/provider/llvm/values.sn) |
AMDGPU stack slots live in the private address space; each is cast to a flat pointer next to its alloca. |
| Linking | built-in linkers (ELF; Mach-O; COFF; wasm) | Static executables, no libc. See Linking. |
A native backend for a new architecture needs only the kernel entry and _start; the std layer is
shared with the LLVM backend.
Verification
Each target is verified at the strongest level its environment allows.
| Level | Targets | Meaning |
|---|---|---|
| Native run | x86_64-linux, x86_64-android; arm64-darwin on an arm64 Mac |
the built executable runs on the host |
| qemu-user | aarch64, riscv64, loongarch64, powerpc64le, s390x, mips64el, sparcv9, i686 and armv7 Linux; aarch64 and armv7 Android | examples run under qemu-<arch> and their output is compared with x86_64 |
| Wine | x86_64-windows-msvc |
the PE executable runs under Wine |
| FreeBSD VM | x86_64-freebsd, aarch64-freebsd and riscv64-freebsd under qemu system emulation |
examples and target tests run in a FreeBSD 14.5 guest |
| System-call translator | x86_64-freebsd |
examples run on Linux under tools/tests/freebsd_abi_emulator.c (single-threaded programs) |
| WASI runner | wasm32-wasi |
wasmtime |
| node | wasm32-browser |
the module's _start result is the exit status |
| Built | x86_64-darwin, aarch64-windows-msvc |
the executable links; tools/audit/macos_imports_audit.py checks Mach-O imports against the SDK |
| qemu-system-arm | thumbv6m-none-eabi (microbit), thumbv7m-none-eabi (lm3s6965evb, mps2-an385), thumbv7em-none-eabi and thumbv7em-none-eabihf (mps2-an386, mps2-an500) |
tools/tests/cortex_m_tests.sh links images with ld.lld and the linker scripts, runs them with semihosting and compares the exit status |
| Objects and assembly | other freestanding and accelerator targets | the compiler produces objects or assembly |
tools/tests/target_tests.sh runs tests/{en,ar}/target on every target their // أهداف:
header names: check, then an executable (or an object or assembly), then a run where the host
can run it, comparing the exit status and standard output. On an x86_64 Linux host with qemu-user,
wasmtime, node and Wine, it runs 192 of the 296 test-target pairs and builds the other 104.
Every program in examples/en (90 of the 93 files; the other three are two module helpers
and a C-library FFI example) gives the same output and exit status as on x86_64 Linux under
qemu-user on every Linux and Android architecture, under wasmtime (except the examples that need
processes or the host environment), and on the FreeBSD x86_64, aarch64 and riscv64 VMs together
with the target tests. The per-target counts are in
LLVM targets progress. tools/tests/freebsd_examples.sh
runs the x86_64 examples on a Linux host through a ptrace system-call translator
(tools/tests/freebsd_abi_emulator.c).
Cross-checking a Linux target
./build/sultanc --backend=llvm --target=riscv64-linux-gnu examples/en/fs/files.sn -o files
qemu-riscv64 ./files
Regenerating the FreeBSD tables
The generators read only the sys/ and include/ headers of a FreeBSD source tree.
git clone --depth 1 -b releng/14.2 https://github.com/freebsd/freebsd-src
python3 tools/generate/generate_freebsd_syscalls.py --src freebsd-src
python3 tools/generate/generate_freebsd_uapi.py --src freebsd-src
Running FreeBSD under qemu
The official VM images run under qemu's system emulation (TCG; no KVM needed). On Ubuntu 24.04 the
packages are qemu-system-arm qemu-system-misc qemu-efi-aarch64 u-boot-qemu opensbi. The images
are in https://download.freebsd.org/releases/VM-IMAGES/14.5-RELEASE/{aarch64,riscv64}/Latest/:
FreeBSD-14.5-RELEASE-arm64-aarch64-ufs.qcow2.xz and
FreeBSD-14.5-RELEASE-riscv-riscv64-ufs.qcow2.xz. Check them against CHECKSUM.SHA256. The 14.2
release is no longer on the mirror; 14.5 runs binaries built against the 14.2 headers. Converting
an image with qemu-img convert -c -O qcow2 halves its size.
# aarch64: UEFI firmware, virtio PCI devices, SSH on host port 2223
qemu-system-aarch64 -M virt -cpu cortex-a72 -smp 4 -m 2048 -display none \
-bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd \
-drive file=arm64-aarch64.qcow2,if=none,id=hd0,format=qcow2 -device virtio-blk-pci,drive=hd0 \
-netdev user,id=n0,hostfwd=tcp:127.0.0.1:2223-:22 -device virtio-net-pci,netdev=n0 \
-serial unix:ser-aarch64.sock,server,nowait -monitor none
# riscv64: OpenSBI, then U-Boot (S-mode), which boots the image's EFI loader; SSH on 2224
qemu-system-riscv64 -M virt -smp 4 -m 2048 -display none \
-bios /usr/lib/riscv64-linux-gnu/opensbi/generic/fw_jump.bin \
-kernel /usr/lib/u-boot/qemu-riscv64_smode/u-boot.bin \
-drive file=riscv-riscv64.qcow2,if=none,id=hd0,format=qcow2 -device virtio-blk-device,drive=hd0 \
-netdev user,id=n0,hostfwd=tcp:127.0.0.1:2224-:22 -device virtio-net-device,netdev=n0 \
-serial unix:ser-riscv64.sock,server,nowait -monitor none
The images log root in on the serial console without a password. Over the console socket,
install a key in /root/.ssh/authorized_keys, add PermitRootLogin prohibit-password to
/etc/ssh/sshd_config, then run sysrc sshd_enable=YES; service sshd keygen; service sshd start
(sshd is off in the images, and generating the host keys takes several minutes on riscv64 under
TCG). Copy a program with scp -P 2223 (or 2224) and run it over ssh. Under TCG on a 4-core host
the aarch64 VM boots in about 3 minutes and the riscv64 VM in about 10, and riscv64 programs run
roughly ten times slower than aarch64 ones.
The amd64 image prints only to the video console. Without a network, a run can still be driven
from the host: QEMU's monitor sendkey types at the console (root, then a command line),
screendump shows the screen, and the guest writes its results to the serial port
(... > /dev/cuau0), which -serial unix:... hands to the host. The programs reach the guest on
an ISO image (genisoimage -R) mounted with mount_cd9660 (-cdrom on amd64, virtio-scsi on
aarch64, and a read-only virtio-blk-device on riscv64, where it is /dev/vtbd1).
Self-hosting on Android and FreeBSD
A compiler built for x86_64-android, aarch64-android, x86_64-freebsd or aarch64-freebsd
runs there, compiles programs for its own platform by default and compiles itself. Build one from
an x86_64 Linux host with the native backend (the module source below leaves out the LLVM
provider, as ./build.sh does for a compiler without LLVM):
./build/sultanc --target=x86_64-freebsd \
--module-source=compiler.backend.provider.llvm.llvm_codegen=compiler/backend/provider/llvm/unavailable.sn \
compiler/main.sn -o sultanc
On the target, run it from a directory that holds compiler/ and library/ (or set
SULTANC_STD). The compiler learns where it runs from the host-environment identity in
compiler/runtime/host_environment_model.sn, whose platform codes include Android (6) and
FreeBSD (7); compiler/backend/target/target_registry.sn maps them to the registered targets, so
sultanc program.sn with no --target builds for the platform it runs on.
| Target | check compiler/main.sn --target |
Compiler builds and links | Run (hello program; the compiler compiling itself) |
|---|---|---|---|
x86_64-android |
passes | yes (33 MB static ELF) | natively on the Linux kernel: hello prints its output; the compiler it builds is byte-identical to itself |
aarch64-android |
passes | yes (31 MB static ELF) | qemu-aarch64: hello prints its output; the compiler it builds is byte-identical to itself (about 3 minutes) |
x86_64-freebsd |
passes | yes (33 MB static ELF, FreeBSD ABI note) | the system-call translator: hello prints its output; the compiler it builds is byte-identical to itself |
aarch64-freebsd |
passes | yes (31 MB static ELF, FreeBSD ABI note) | FreeBSD 14.5 arm64 VM (qemu -cpu neoverse-n1): check passes, hello prints its output; the compiler it builds (about 9 minutes, 1.3 GB resident) is byte-identical to the one the x86_64 Linux compiler builds from the same source directory, and it compiles hello again |
A compiler records the absolute paths of its sources, so two builds compare equal only when they are compiled from the same directory.
On these targets (and on Linux and Windows) aarch64 code uses ARMv8.0 atomics, LDAXR/STLXR
loops, from both the native and the LLVM backend (see Concurrency), so the
executables run on ARMv8.0 cores such as the Cortex-A53, A57 and A72 (Raspberry Pi 3 and 4) as well
as on later ones: an aarch64-linux-gnu compiler checks compiler/main.sn and compiles hello under
qemu-aarch64 -cpu cortex-a57. Only arm64-darwin uses the ARMv8.1 LSE atomics (LDADDAL,
CASAL and the rest), which every Apple Silicon core has. sultanc run is not available on these
hosts: the interpreter's host kernel bridge exists for Linux only, and the run reports
SULTANC-INTERPRETER-UNSUPPORTED-MACHINE-PRIMITIVE. Compile the program and run the executable
instead.
Limitations
- The native backend covers the x86_64 and aarch64 operating-system targets and wasm32; every other target needs the LLVM backend.
sultanc runis not available on Android and FreeBSD hosts.ve-linux-gnuhas no std OS layer.- Freestanding and accelerator targets produce objects or assembly only; Cortex-M images are
linked with
ld.lldoutside the compiler. - Cortex-M: no soft-float routines (float operations without FPU support are rejected), no Cortex-M7 double-precision FPU selection, and only 32-bit atomic loads, stores and fences in std.
x86_64-darwinandaarch64-windows-msvcare built but not run.wasm64-unknown-unknownproduces objects only.sultanc check compiler/main.snpasses for the hosted targets only: the compiler uses files and processes.wasm32-wasihas no processes, threads or file locks, and its sockets are limited to preopened ones.
See also
- Platform dependencies: every system interface std imports, per platform.
- Concurrency: thread support per OS.
- MSIR and code generation: how backends and linkers fit together.
- Bootstrap: building the compiler with or without LLVM.