Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Salam platform & word-size support

Salam platform & word-size support

This document explains which CPU word sizes (32-bit / 64-bit) and operating systems Salam supports, how each backend behaves, and how the release pipeline produces binaries for them.

TL;DR

Can a 32-bit machine use i64 and f64? - Yes.

“32-bit” refers to the pointer / word width, not the widest value you can compute with. Nothing about 64-bit numeric types is lost on a 32-bit CPU:

TypeOn a 32-bit target
i64 / u64Fully supported. Add/sub are inlined as 32-bit register pairs; multiply/divide/modulo call the compiler’s runtime helpers (__divdi3, __moddi3, __udivdi3, …). Correct, just a few instructions slower than native 64-bit.
f64 (double)Fully supported and usually native - the FPU (x87 / SSE2 / NEON) operates on 64-bit doubles regardless of pointer width.
i32 / f32Native.

What actually changes on 32-bit is only:

So the only real porting work is making sure the compiler never assumes a pointer or size_t is 64 bits. That is exactly what the changes below address.

How each backend handles word size

C backend (salam build) - portable by construction

Salam types lower to standard C types: i64int64_t, f64double, sizeof(T)→C’s sizeof, pointers→native C pointers. The system C compiler (gcc / tcc / clang) then resolves every width for the actual target. Build the generated C with a 32-bit compiler and you get correct 32-bit code for free - no special handling in Salam is required.

LLVM backend (salam llvm) - now width-aware

Salam emits textual LLVM IR. Previously every size_t-typed position (malloc, realloc, memcpy, memmove, snprintf, strlen) and the internal helpers were hardcoded to i64, which is wrong on a 32-bit target - the IR declarations would not match the real libc ABI (most visibly, snprintf’s vararg layout breaks when its size_t argument is the wrong width).

The backend now carries a target usize width derived from the target triple:

salam llvm prog.salam --emit-llvm                           # host width (i64 on x86-64)
salam llvm prog.salam --emit-llvm --target=i686-linux-gnu   # 32-bit: usize = i32

Width classification lives in ll_target_ptr_bits() (compiler/src/llvm/codegen_llvm_type.c); the prologue generator is ll_emit_prologue() (compiler/src/llvm/codegen_llvm.c).

Note: hash values and %lld/%llu formatting stay i64 on every target - that is correct (64-bit math just runs via the helper routines on 32-bit).

Known follow-up: full 32-bit correctness for stdlib FFI size declarations would benefit from a real pointer-width language type (usize/isize) so std/* does not hard-code u64 for sizes. Until then, prefer the C backend for production 32-bit builds. The C backend already handles this correctly.

Release matrix (GitHub Actions: .github/workflows/compiler-release.yml)

JobOS / ArchWord sizeBackend in the shipped binary
build-linuxLinux x86-6464Embedded static LLVM + C backend
build-macosmacOS (arm64/x64)64Embedded static LLVM + C backend
build-windowsWindows x6464Embedded static LLVM + bundled tcc
build-windows-i686Windows x8632Embedded LLVM if it links on i686, else C backend; bundled 32-bit tcc
build-linux-i686Linux x8632C backend + interpreter
build-linux-armLinux aarch64 / armhf64 / 32C backend (cross-compiled, smoke-tested under QEMU)

The three 32-bit/cross jobs are marked continue-on-error, and the release step attaches their artifacts only if present - a flaky 32-bit or cross build can never block the release of the core 64-bit platforms.

Why not embedded LLVM everywhere on 32-bit?

Recommendation