Standard library · Compiler and tools

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, windows or musl. They do not validate it: a misspelled triple quietly gets the wrong answer.
  • An empty triple means the host machine. So does an architecture PointerBits does not know: it falls back to the host's pointer size instead of reporting an error.
  • Datalayout knows 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.
  • IsGlibc means "Linux and not musl". An Android triple such as aarch64-linux-android contains linux, 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.

llvm-triple-guide-sizes.salam
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
end
Outputx86_64-unknown-linux-gnu 64 bit pointers, node is 24 bytes i686-unknown-linux-gnu 32 bit pointers, node is 12 bytes wasm32-unknown-wasi 32 bit pointers, node is 12 bytes aarch64-apple-darwin 64 bit pointers, node is 24 bytes

The 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.

llvm-triple-guide-output.salam
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
end
Outputapp for x86_64-unknown-linux-gnu needs glibc on the machine it runs on app for x86_64-linux-musl can be fully static app.exe for x86_64-w64-windows-gnu links against the Windows C runtime app for aarch64-apple-darwin uses the system C library

When 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.

llvm-triple-guide-layout.salam
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
end
Outputx86_64-pc-windows-msvc pointers: 64 bits, little endian windows: true | glibc: false aarch64-apple-darwin pointers: 64 bits, little endian windows: false | glibc: false riscv64-unknown-linux-gnu pointers: 64 bits, little endian windows: false | glibc: true armeb-unknown-linux-gnueabi pointers: 32 bits, big endian windows: false | glibc: true wasm32-unknown-wasi pointers: 32 bits, unknown windows: false | glibc: false no built-in layout, let LLVM choose aarch64-linux-android pointers: 64 bits, little endian windows: false | glibc: true

Two 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(): str
The data layout of the host machine.
llvm-triple-Layout.salam
import 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
end
Outputx86_64-unknown-linux-gnu 64 false true e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128 aarch64-unknown-linux-musl 64 false false e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128 aarch64-apple-darwin 64 false false e-m:o-i64:64-i128:128-n32:64-S128 x86_64-pc-windows-msvc 64 true false e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128 i686-w64-windows-gnu 32 true false unknown; LLVM works it out itself wasm32-unknown-wasi 32 false false unknown; LLVM works it out itself true

Datalayout

func Datalayout(t: str): str
The LLVM data layout string for the triple: the byte order, the pointer size and the alignment of the types; empty for a target it does not know, such as WebAssembly. An empty triple gives the host layout.

The example for this item is shown with the ones above: llvm-triple-Layout.salam

PointerBits

func PointerBits(t: str): int
The pointer size of the target in bits: 64, 32, or 16 for AVR and MSP430. An empty or unknown triple gives the host's size.

The example for this item is shown with the ones above: llvm-triple-Layout.salam

IsWindows

func IsWindows(t: str): bool
The triple names Windows, MinGW or Win32; an empty triple asks about the host.

The example for this item is shown with the ones above: llvm-triple-Layout.salam

IsGlibc

func IsGlibc(t: str): bool
The target is Linux and not musl, macOS, Windows or WebAssembly. Android triples also count as glibc. False for an empty triple.

The example for this item is shown with the ones above: llvm-triple-Layout.salam