llvm/triple
The data layout and target features worked out from a triple, with no LLVM needed.
import llvm.triple
A target triple, such as x86_64-unknown-linux-gnu, names the architecture, the vendor, the operating system and the environment. Without linking against LLVM, this package gives from it what a compiler needs before it generates code, such as the pointer size and the data layout.
The parts are separated by dashes and the later ones may be missing: x86_64-linux-musl has no vendor, and aarch64-apple-darwin no environment. The functions here only read the text, so they work the same on every machine and for targets you cannot run, which is what a cross compiler needs. Salam's own LLVM backend uses them to pick the data layout and the C library of the target. To create a real LLVM target machine for a triple, use llvm.
Things to know
- The functions look for known words anywhere in the triple, such as
x86_64,windowsormusl. They do not validate it: a misspelled triple quietly gets the wrong answer. - An empty triple means the host machine. So does an architecture
PointerBitsdoes not know: it falls back to the host's pointer size instead of reporting an error. Datalayoutknows x86-64 (Linux, Windows and macOS), 32 bit x86 outside Windows and macOS, AArch64, RISC-V 64 and 32 bit ARM. For anything else, WebAssembly included, it returns an empty string. Windows on ARM gets the Linux AArch64 layout.IsGlibcmeans "Linux and not musl". An Android triple such asaarch64-linux-androidcontainslinux, so it counts as glibc although Android uses Bionic.
Laying out a struct for another target
The size of a pointer decides the size of every struct that holds one, and therefore every offset a code generator writes. PointerBits gives it for any triple. The program below lays out a list node with a name pointer, a 32 bit age and a next pointer, aligning each field to its own size as C does.
import llvm.triple
func align_up(n: int, a: int): int:
ret (n + a - 1) / a * a
end
func node_size(target: str): int:
ptr := triple.PointerBits(target) / 8
mut size := ptr
size = align_up(size, 4) + 4
size = align_up(size, ptr) + ptr
ret align_up(size, ptr)
end
func main:
each target in ["x86_64-unknown-linux-gnu", "i686-unknown-linux-gnu", "wasm32-unknown-wasi", "aarch64-apple-darwin"]:
println target, triple.PointerBits(target), "bit pointers, node is", node_size(target), "bytes"
end
endThe node is twice as big on 64 bit targets: two eight byte pointers plus a four byte integer padded to eight. On a 32 bit target, WebAssembly included, every field is four bytes and nothing is padded. For an exact answer for every type, give the string from Datalayout to LLVM, as the llvm package does.
Choosing the file name and libc
A build tool makes a few decisions straight from the triple: Windows programs end in .exe, a glibc program needs a compatible glibc wherever it runs, and a musl program can be linked fully static and copied to any Linux machine. IsWindows and IsGlibc answer the first two; anything else can be read from the triple with str.
import llvm.triple
import str
func output_name(name: str, target: str): str:
if triple.IsWindows(target):
ret name + ".exe"
end
ret name
end
func libc_note(target: str): str:
if triple.IsWindows(target):
ret "links against the Windows C runtime"
else triple.IsGlibc(target):
ret "needs glibc on the machine it runs on"
else str.Contains(target, "musl"):
ret "can be fully static"
end
ret "uses the system C library"
end
func main:
each target in ["x86_64-unknown-linux-gnu", "x86_64-linux-musl", "x86_64-w64-windows-gnu", "aarch64-apple-darwin"]:
println output_name("app", target), "for", target
println " " + libc_note(target)
end
endWhen Datalayout returns an empty string the target is one this package does not know. That is not an error: leave the data layout out of the module and LLVM fills in its own default for the triple.
What the package knows about a target
This program asks every function about six targets. The first letter of a data layout string is the byte order: e for little endian, E for big endian. The rest lists the size and alignment of pointers and integer types, in LLVM's own format.
import llvm.triple
import str
func byte_order(data_layout: str): str:
if data_layout.len() == 0:
ret "unknown"
end
if str.StartsWith(data_layout, "E"):
ret "big endian"
end
ret "little endian"
end
func main:
targets := ["x86_64-pc-windows-msvc", "aarch64-apple-darwin", "riscv64-unknown-linux-gnu", "armeb-unknown-linux-gnueabi", "wasm32-unknown-wasi", "aarch64-linux-android"]
each t in targets:
data_layout := triple.Datalayout(t)
println t
println " pointers:", triple.PointerBits(t), "bits,", byte_order(data_layout)
println " windows:", triple.IsWindows(t), "| glibc:", triple.IsGlibc(t)
if data_layout.len() == 0:
println " no built-in layout, let LLVM choose"
end
end
endTwo answers deserve a second look. WebAssembly has no built-in layout, so its byte order is unknown here even though WebAssembly is little endian. Android reports glibc: true; a build tool that ships Android binaries should test for android itself before it trusts IsGlibc.
Functions
HostDatalayout
func HostDatalayout(): strimport llvm.triple
func main:
each target in ["x86_64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "aarch64-apple-darwin", "x86_64-pc-windows-msvc", "i686-w64-windows-gnu", "wasm32-unknown-wasi"]:
println target, triple.PointerBits(target), triple.IsWindows(target), triple.IsGlibc(target)
dl := triple.Datalayout(target)
if dl.len() > 0:
println " ", dl
else:
println " unknown; LLVM works it out itself"
end
end
println triple.HostDatalayout().len() > 0
endDatalayout
func Datalayout(t: str): strThe example for this item is shown with the ones above: llvm-triple-Layout.salam
PointerBits
func PointerBits(t: str): intThe example for this item is shown with the ones above: llvm-triple-Layout.salam
IsWindows
func IsWindows(t: str): boolThe example for this item is shown with the ones above: llvm-triple-Layout.salam
IsGlibc
func IsGlibc(t: str): boolThe example for this item is shown with the ones above: llvm-triple-Layout.salam