Compile-Time Context
Four values describe the build a program is part of. The compiler fills them in before compiling starts, and Core declares them, so each is imported by its # name:
import Core::{ #target, #build, #compiler, #source };
| Value | Type | Describes |
|---|---|---|
#target | Target | the machine the program is built for |
#build | Build | the build profile, its options and the output |
#compiler | Compiler | the compiler doing the build |
#source | Source | the place in the source where the read is written |
A fifth, #config, carries values the build is given — see Config. The struct and enum types (Target, OperatingSystem, BuildMode, …) are ordinary Core declarations; import them too when code names them, as in OperatingSystem::Linux or BuildMode::Debug.
Fields are read with ., are lowerCamelCase, and are fixed for the whole build. A read can drive a when, initialize a scalar constant, or appear in any expression, where it compiles to a constant. Text fields (char8[..]) work in expressions and conditions; a const of slice type, though, must be initialized with a literal, so const Date: char8[..] = #build.date; is refused with a constant sequence must be initialized with an array literal or a string literal — use a let instead.
#target
The machine the program will run on — not the machine compiling it. rux build --target linux-x86_64 on Windows answers every field for Linux.
| Field / query | Type | Notes |
|---|---|---|
os | OperatingSystem | the operating system |
arch | Architecture | the processor architecture |
abi | ApplicationBinaryInterface | the calling and layout conventions |
endian | Endianness | byte order; .Little on every supported target |
pointerBits | uint | 64 on every supported target |
dataModel | DataModel | the C widths of long and pointers |
objectFormat | ObjectFormat | the object format the linker emits |
triple | char8[..] | the canonical <os>-<arch> name --target takes |
HasFeature(feature) | bool | whether the build enables a CPU feature |
The eight supported targets:
triple | os | arch | abi | dataModel | objectFormat |
|---|---|---|---|---|---|
windows-x86_64 | .Windows | .X86_64 | .WindowsX64 | .LLP64 | .COFF |
windows-aarch64 | .Windows | .AArch64 | .AAPCS64 | .LLP64 | .COFF |
linux-x86_64 | .Linux | .X86_64 | .SystemV | .LP64 | .ELF |
linux-aarch64 | .Linux | .AArch64 | .AAPCS64 | .LP64 | .ELF |
macos-x86_64 | .macOS | .X86_64 | .SystemV | .LP64 | .MachO |
macos-aarch64 | .macOS | .AArch64 | .AAPCS64 | .LP64 | .MachO |
freebsd-x86_64 | .FreeBSD | .X86_64 | .SystemV | .LP64 | .ELF |
freebsd-aarch64 | .FreeBSD | .AArch64 | .AAPCS64 | .LP64 | .ELF |
HasFeature takes a TargetFeature. A build for the machine running the compiler reports the features that compiler was itself built to use; a build for any other target reports none. Inside a when condition the shorthand .AVX2 works; in an ordinary expression write TargetFeature::AVX2. An unknown name is an error (unknown target feature '.MMX').
import Core::{ #target };
when #target.arch == .X86_64 && #target.HasFeature(.AVX2) {
func Kernel() -> char8[..] { return "avx2"; }
} else {
func Kernel() -> char8[..] { return "scalar"; }
}
#build
The build profile and what it produces. Every field is the same everywhere in one build.
| Field | Type | Debug profile | Release profile |
|---|---|---|---|
profile | char8[..] | "Debug" | "Release" |
mode | BuildMode | .Debug | .Release |
optimization | OptimizationMode | .None | .Speed |
debugAssertions | bool | true | false |
debugInfo | bool | true | false |
| Field | Type | Value |
|---|---|---|
isTest | bool | true in a rux test build |
outputKind | OutputKind | the artifact: .Executable, .SharedLibrary, .StaticLibrary or .SourceLibrary |
timestamp | uint64 | when the build started, in seconds since the Unix epoch |
date | char8[..] | timestamp as YYYY-MM-DD, in UTC |
time | char8[..] | timestamp as HH:MM:SS, in UTC |
profile is a name, and a project may define profiles of its own; branch on mode, which every profile has. debugAssertions decides whether DebugAssert is kept — when it is false the call is removed and its arguments are not evaluated. The timestamp is taken once per build, and honours the SOURCE_DATE_EPOCH environment variable for reproducible builds.
import Core::{ #build, BuildMode };
import Io::PrintLine;
func Main() -> int {
when #build.mode == BuildMode::Debug {
PrintLine("debug build of {} at {}", #build.date, #build.time);
}
return 0;
}
#compiler
| Field / query | Type | Notes |
|---|---|---|
version | SemanticVersion | the compiler's version, with major, minor, patch (uint) |
HasFeature(name) | bool | whether the compiler reports a named feature |
SemanticVersion is an ordinary struct with the comparison operators == != < <= > >= and the methods Compare, IsEqualTo, IsLessThan, IsAtMost, IsGreaterThan and IsAtLeast, all of which work in a when. A version to compare against is a struct literal constant — a constructor call is a run-time call and is refused (call to 'SemanticVersion' is not a compile-time value):
import Core::{ #compiler, SemanticVersion };
const Rux040 = SemanticVersion { major: 0, minor: 4, patch: 0 };
when #compiler.version.IsAtLeast(Rux040) {
const HasDefines: bool = true;
} else {
const HasDefines: bool = false;
}
HasFeature is the better test when code needs one capability rather than a release: it names what is needed and stays right however releases are numbered. Its argument must be a string literal; an unknown name is simply false. Rux 0.4.0 reports:
| Feature name | Feature name | Feature name |
|---|---|---|
"conditional-compilation" | "namespaced-intrinsics" | "target-intrinsics" |
"build-intrinsics" | "compiler-feature-detection" | "source-location-defaults" |
"extern-symbol-names" | "link-attribute" | "no-return-attribute" |
#source
Each read of #source describes the place where that read is written — the expression itself, not the function around it or its caller.
| Field | Type | Value |
|---|---|---|
line | uint | the one-based line of the read |
column | uint | the one-based column of the read |
file | char8[..] | the file name as the build names it, such as "Main.rux" |
fileName | char8[..] | the file's last path component, such as "Main.rux" |
filePath | char8[..] | the path inside the package, such as "Src/Main.rux" |
function | char8[..] | the enclosing function, qualified for a method ("Point::Length") or a module function ("Tools::Parse") |
module | char8[..] | the enclosing module path |
#source.module names only module blocks: a read inside module Tools is "Tools", one inside its nested module Inner is "Tools::Inner", and one outside every module is "". A file is not a module, so its name never appears.
A helper that reads #source in its own body always reports its own body. To report where it was called from, the caller reads #source and passes the value:
import Core::{ #source };
import Io::PrintLine;
func LogAt(line: uint, message: char8[..]) {
PrintLine("line {}: {}", line, message);
}
func Main() -> int {
LogAt(#source.line, "first call"); // the line of this call
LogAt(#source.line, "second call"); // and of this one
return 0;
}
A parameter default does that passing for the caller. A #source read written as a default is expanded at each call, so it describes the call, not the parameter list: with func LogAt(message: char8[..], line: uint = #source.line), every LogAt("…") reports its own line, and a #source.function default names the function the call is in.
Enum reference
Every enum here is a Core declaration with a uint8 representation.
| Enum | Variants |
|---|---|
OperatingSystem | Unknown, FreeBSD, Linux, macOS, Windows |
Architecture | Unknown, AArch64, X86_64 |
ApplicationBinaryInterface | Unknown, AAPCS64, SystemV, WindowsX64 |
Endianness | Big, Little |
DataModel | Unknown, LLP64, LP64 |
ObjectFormat | Unknown, COFF, ELF, MachO |
TargetFeature | AVX, AVX2, AVX512, NEON, SSE2, SSE3, SSE41, SSE42, SSSE3, SVE |
BuildMode | Debug, Release |
OptimizationMode | None, Size, Speed |
OutputKind | Executable, SharedLibrary, StaticLibrary, SourceLibrary |
No supported build produces an Unknown variant; it exists so a provider can describe a target the compiler has no name for. The variant is .macOS, with a lower-case m.
See also
- Conditional compilation — branching on these values
- Config —
#config, the build's own values - Intrinsics — how
Coredeclares them - API:
Target,Build,Compiler,Source - Learn: Target, Build mode, Source location