Language Reference

<!-- verified-against: seed-llvm; examples: docs-examples-test -->

This page is the user-oriented reference for the current seed Language Specification. The complete grammar and operational semantic chapters live under compiler/llvm. For current limits and closure claims, see Current Status.

Source encoding and names

.sd files are UTF-8. Comments and string contents may contain Unicode. string owns valid UTF-8 and str is a borrowed read-only UTF-8 pointer-plus-byte-length view. Text length is measured in bytes; Unicode scalar, grapheme, normalization, and locale operations belong in libraries.

Source identifiers intentionally use ASCII:

Kind Rule Example
variable, function, field, module [a-z][a-z0-9_]* my_document
struct, enum, union, trait, generic parameter [A-Z][a-zA-Z0-9]* Document, T
enum variant [A-Z][a-zA-Z0-9]* Some, Ok, Red
constant [A-Z][A-Z0-9_]* MAX_DOCUMENT_SIZE

The formal language name is seed, always lowercase. Value names use lower snake case; nominal types and enum variants use UpperCamelCase and match [A-Z][a-zA-Z0-9]*. Constants use upper snake case. _ is the discard binding; a leading underscore may mark an intentionally unused private binding.

Line comments start with //. Block comments /* ... */ nest. The clean grammar does not use # comments.

The source loader validates the whole file as UTF-8 before lexing and rejects embedded NUL; decoded string literals are validated as well.

Bindings, constants, and mutation

const MAX_COUNT: usize = 100

fn release(ticket: i64) {}

fn main() -> i64 {
    let answer = 42
    let mut count: usize = 0
    count += 1
    let linear ticket = 7
    release(ticket)
    if count < MAX_COUNT { answer } else { 0 }
}

The current compiler also accepts typed scalar expressions, private fixed u32 constant tables such as const TABLE: [4]u32 = [1 as u32, 2 as u32, 3 as u32, 4 as u32], static text, tuples, and structs. Indexed and field reads use immutable static storage with ordinary bounds checks. Public aggregate values retain their recursive representation in schema-14 source-elided builds.

The constant type annotation is optional, matching const_decl = "const", constant_ident, [ ":", type ], "=", expression: an unannotated constant infers its type from its initializer (const MAX_ITEMS = 64 is i64, const OFFSETS = [1, 4, 9] is [3]i64), including static text, tuples, structs and named constants. An initializer whose type cannot be inferred needs an explicit annotation and is rejected with R037 at the declaration.

Bindings are immutable unless declared mut. Assignment to a binding, field, or index through a binding requires that binding to be mutable. linear creates an exactly-once obligation.

let also accepts irrefutable patterns: tuple destructuring (let (a, b) = pair), struct destructuring (let Point { x, y } = origin), wildcards (_), and nesting. Each named leaf becomes an ordinary binding moved out of the initializer's component, so ownership, linearity, and cleanup track components independently. Refutable patterns (variants, literals) are rejected (error[B043]); use match for those.

A let annotation accepts any type valid in a parameter position (arrays, slices, tuples, pointers, function types, generics, qualified names) and is checked against the inferred initializer type (error[H059] on mismatch). Compile-time layout queries size_of(type) and align_of(type) are evaluated against the active target and are valid in constant contexts — const initializers and array lengths such as [size_of(i64)]u8; elsewhere they are rejected with error[H129].

To discard a return value, use let _ = expression which evaluates and cleans up the value without creating a binding; repeated _ in one scope is legal and _ is never accessible. let _unused = expression with a leading underscore also marks an intentionally unused binding but creates a named binding.

Functions, methods, and returns

struct Document {
    title: string
}

fn add(left: i64, right: i64) -> i64 {
    left + right
}

@borrow
fn Document.title_size() -> usize {
    len(self.title)
}

@mut
fn Document.rename(next: string) {
    self.title = move next
}

unsafe fn read_register(address: *mmio mut i64) -> i64 {
    raw_volatile_read<i64>(address, 0)
}

A block's final expression is its value. return expression returns early; the clean compiler does not use ret. A method declaration includes the nominal type before the dot and receives implicit self; do not write self in its parameter list. fn TypeName.drop() is a consuming destructor. @borrow on a method or trait method gives self a shared lexical loan. @mut instead requires a mutable addressable receiver, holds an exclusive loan for the call, and makes mutations to self visible to the caller. The two attributes are mutually exclusive.

str returns are provenance-based, not restricted to methods: a free function may return a static or input-derived view. A view derived from a local owned string/bytes value cannot escape (V001); return the owner when ownership must leave the function. There is no lifetime syntax.

Public declarations use pub. Foreign declarations use explicit extern calling convention/link metadata and are unsafe to call until wrapped by an audited safe function. Platform-C argument classification is target-specific: Windows x86-64 passes aggregate parameters larger than eight bytes indirectly, so str, slices, and equivalent C structs interoperate with MinGW/MSVC-style callees.

Core types

Category Types
control void, never, bool
integer i8, i16, i32, i64, u8, u16, u32, u64, isize, usize; byte aliases u8
float f32, f64
text/buffer str, string, bytes, shared_bytes
formatting format_args (ephemeral)
aggregate/view [N]T, []T, mut []T, tuples, nominal structs/enums/unions
failure ?T, Result<T, E>, alloc_error
callable non-owning fn(T) -> U, escaping box fn(T) -> U
low-level raw pointers plus user, kernel, physical, mmio, and dma address spaces
exact-once linear T

AllocResult<T> abbreviates Result<T, alloc_error> without introducing a new type identity, representation, cleanup rule, or ABI.

Hosted main may return Result<i64, E>. Ok(status) exits with that status; alloc_error uses the fixed allocation diagnostic. Another E must be Copy, have no destructor, and implement @borrow fn E.display() -> str; its view is written once before status 1, after ordinary cleanup.

@static_view extern fn ... is the audited FFI declaration for a returned str/slice whose storage is process/static lifetime. The attribute is rejected on ordinary seed functions and does not make the foreign call safe.

Effect attributes may annotate fn declarations: @no_alloc, @allocates, @blocking, @panics, @io, @interrupt_safe. Qualified callback types use @no_runtime_effects fn(...) -> T to declare a complete empty effect summary. An unsafe struct may declare @unsafe_send to assert the send capability, or @repr("null_niche") to enable null-pointer optimization on a single raw-pointer field for ?Wrapper.

Integer add/subtract/multiply wraps deterministically at the type width. Division by zero, signed minimum divided by -1, invalid shifts, invalid safe casts, and failed safe indexing trap. Release profiles preserve these rules. Explicit integer widening uses source signedness; narrowing keeps low bits and equal-width signedness changes preserve bits. Integer/float and float-width casts follow IEEE-754 conversion. Float-to-integer requires unsafe and a finite destination-representable input.

Byte literals use b'A' and have type u8. One raw ASCII byte or one \\n/\\r/\\t/\\0/\\\\/\\'/\\xNN escape is accepted. Use b'\\xFF' for a non-ASCII byte; seed deliberately has no locale-dependent character literal type.

Integer literals are decimal, 0x hexadecimal, or 0b binary, with _ as an optional digit separator (20_000_000, 0xFF, 0b1010). Every accepted form reaches codegen everywhere a literal is allowed, including array literals and repeat literals.

A repeat array literal fills a fixed array from one literal: let slots: [4]usize = [0; 4], [255; 3], [[0; 2]; 3]. The element type comes from the annotation or the array's expected type, so no cast is needed. The value must be a literal (or a cast of one, or an array of those) — a place or call value is rejected as B048 rather than silently evaluated once per element — and the count must be a positive integer literal (B049) of at most 65536 (B050), because the elements are materialized individually; larger fills belong to a heap buffer or an explicit loop.

Structs, ADTs, and unions

struct Document {
    title: string
}

enum LookupResult {
    Found(Document)
    Missing
}

union ScalarBits {
    integer: u64
    decimal: f64
}

trait Identity {
    fn id() -> i64
}

A struct literal is Document { title: owned_title }. An ADT constructor is qualified, for example LookupResult.Found(value), and its payload is inspected through exhaustive match. A payload-free enum:

enum Color { Red Green Blue }

Raw union construction/access is unsafe. Safe tagged alternatives use ADTs.

Options, results, and propagation

fn find(enabled: bool) -> ?i64 {
    if enabled { Some(42) } else { None }
}

fn allocate_name(view: str) -> Result<string, alloc_error> {
    view.to_string()
}

Some(value) and expected-type None construct an option. Ok(value) and Err(problem) construct a result. ? consumes the container, yields its success/present payload, or returns the corresponding failure from the current function. Result propagation may change the success type but must preserve the error type. It performs no implicit allocation or clone.

is_some/is_none and is_ok/is_error inspect by shared borrow. unwrap_or(fallback) consumes the container and cleans the unselected owned value exactly once. or_abort(), expect(message), and main-only or_exit(status) are explicit application boundaries; they never hide inside ordinary ? propagation.

? can cross a spawn boundary when the body is fallible: spawn { fallible_work()? } stores Result<T, E> into a join cell and the spawn expression yields a task_join handle whose wait() returns Result<T, E>; payload and error must be copy types without destructors. A ? inside a closure nested in a void spawn body is still rejected.

Control flow and patterns

The implemented control forms are expression-oriented if/else if/else, while, loop, for, exhaustive match, blocks, break, continue, and return. else if is the required spelling; elif is not accepted. Match arms need no commas unless the boundary is ambiguous: an arm whose body ends in an expression followed by a (/[ pattern requires the comma (B044). Patterns cover bindings/discards, literals, tuples, structs, and qualified ADT variants; guards preserve a move-only scrutinee until the selected arm actually transfers it. _ is a true discard binding: let _ = expr evaluates and cleans up without binding, repeated _ in one scope is legal, and _ is never accessible. ref is a contextual pattern modifier that binds a move-only payload by read-only borrow when the scrutinee is a borrowed place (Some(ref info) on a @borrow self field); ref bindings cannot be moved or mutated, ref _ discards without moving, and a match whose every move-only payload is ref-bound does not consume the scrutinee. Temporary scrutinees whose arms transfer move-only payloads are materialized into an implicit owned binding, so match fallible() { Ok(v) => ... } compiles without a let x = expr wrapper.

if let and while let are the concise forms over match: if let Some(x) = found { ... } else { ... } and while let Some(x) = next { ... }. Both desugar to a two-arm match, so binding, move/borrow behavior, and cleanup are exactly the match rules. if let Some(...) names its complement None; any other variant pattern keeps a wildcard arm, and H032 rejects that when the scrutinee carries a move-only payload — write the explicit match arms there. An irrefutable pattern (while let name = value) is rejected as H029.

for over ranges, fixed arrays, and slices lowers to an allocation-free index/length loop. for index, value in enumerate(items) adds a usize index without a heap iterator. Move-only elements require explicit indexing/ownership handling.

Text, views, and conversions

Typed phase-1/2 interpolation is allocation-free unless explicitly materialized:

use "file"

fn main() -> Result<i64, alloc_error> {
    let name = "widget"
    let count = 3
    write_stdout_format(f"name={name} count={count} braces={{ok}}\n")
    let message = f"count={count}".to_string()?
    Ok(len(message) as i64)
}

Expressions evaluate once from left to right. Supported values are str, borrowed string, bool, and builtin integers — as arbitrary expressions, not just names: calls, operators, and nesting all work (f"{a + b}", f"{p.distance()}"). The ephemeral format_args result must be passed directly or consumed by .to_string()?; it cannot be assigned with let, returned, captured, or stored. Integer/text/bool width, fill, alignment and integer base specifications are implemented. Custom types use trait Display { @borrow fn display() -> str } with direct static dispatch. Floats support default general output plus f/F, e/E, and g/G with precision through 18. Custom :? formatting uses the analogous borrowed Debug.debug() -> str trait.

Generics and traits

Generic parameters use UpperCamelCase. Definitions are checked against their bounds, then concrete reachable instances are monomorphized. A parameter may declare multiple trait bounds with + (fn f<T: Metric + Named>(x: T)); every bound must name a declared trait, the argument must implement all of them (error[H072] per missing bound), and trait methods resolve through any bound. Impl blocks may themselves be generic: impl<T: Show> Show for Pair<T> implements the trait for every instance whose arguments satisfy the impl's bounds; the target arguments must be exactly the impl's parameters in order (error[P042]), and a generic impl overlapping another impl of the same trait for the same type is rejected (error[R019]). Trait dispatch is static and direct: there are no implicit vtables, boxing, allocation, or clone. Impl completeness, signatures, coherence, and orphan rules are checked. Public generic templates, common instances, ephemeral format_args parameters, null-niche nominal representation, exclusive mutable receivers, qualified callbacks, and boxed callable signatures persist through current schema-17 .sdi. Materialized method instances are exported only by an interface that also owns the generic method definition. A wrapper around an imported generic type therefore records its public contract without an orphan specialization; a source-elided consumer obtains the method template from the wrapper's dependency interface and materializes the concrete instance there.

inline fn requests bounded inlining. A pub inline fn publishes its verified body and deterministic body hash in schema-13 .sdi while retaining an exported fallback symbol. Inline declarations have a 4096-byte source budget; a body change invalidates dependent artifacts. Mutable slice partitions use view.split_at_mut(index) and tuple patterns may declare mutable bindings as (mut left, mut right). The split checks index <= len, allocates nothing, and keeps the common owner exclusively loaned until both siblings die.

Closures and callbacks

fn apply(values: [3]i64, callback: fn(i64) -> i64) -> i64 {
    let mut total = 0
    for value in values {
        total = total + callback(value)
    }
    total
}

fn main() -> i64 {
    let base = 40
    let total = apply([1, 2, 3], |value: i64| base + value)
    let retained = (box |value: i64| base + value).or_abort()
    total + retained(1)
}

|...| and prefix-position || create anonymous callables. Parameter types may be omitted when an expected fn(...) -> T supplies them. A plain closure uses stack capture storage, allocates nothing, and cannot escape its lexical owner. Copy-capable captures copy; owned captures move; captures are immutable. Borrowed, mutable, region-bound, already-moved, and other unsafe-to-store captures are rejected.

box |...| is the explicit escaping form and returns Result<box fn(...) -> T, alloc_error>. The owner may be stored or returned, but is move-only, non-cloneable, and non-sendable. Drop destroys its captures exactly once and frees its allocation. Region allocation remains lexical. There is no implicit conversion between stack and boxed closures.

Seed fn(...) -> T callbacks use a one-pointer code/environment descriptor. C/platform function pointers stay thin and accept named functions only. Closure effects are inferred from the body and checked against qualified callback contracts such as @no_runtime_effects fn() -> void. Recursive closures are unsupported.

Ownership summary

See Ownership for the operational model.

Regions, tasks, and libraries

region(capacity) { body } installs a lexical checked bump allocator. Values, views, or pointers tied to the region cannot escape. Structured tasks captured inside a region join before its backing storage is released.

spawn { body } is structured: every edge leaving the owning lexical scope waits for its task group before cleanup. Capture is copy or move by capability. A fallible body (? inside, or a tail of type Result<T, E>) yields a typed join: the outline stores the outcome in a join cell and handle.wait() returns Result<T, E>; task_result_* remains for manual joins. Channels, typed joins, events, cancellation, and synchronization are provided by the ordinary compiler/llvm/libs/task package. The clean grammar does not have built-in chan<t>, send, recv, or detached spawn.

use "name" imports a source module or a source-elided .sdi interface. Imported public functions are callable with or without their module qualifier: use "process" makes both environment("HOME") and process.environment("HOME") valid. An unknown qualified symbol reports error[H002]: unknown symbol in module.

Unsafe substrate

raw_try_alloc<t>, raw_null<t>, raw_is_null<t>, raw_read<t>, raw_write<t>, raw_free<t>, raw_slice<t>, atomics, volatile access, fences, address-space casts, raw assembly, FFI, syscalls, and raw unions require unsafe. Normal applications should consume safe wrappers from libraries.

Repository launcher

./seed/seed check [--lib] [path|file.sd]
./seed/seed check [--json|--deep] [--report report.json] [--max-ms n] [--max-rss-kib n] [--max-files n] [--max-loc n] [--lib] [path|file.sd]
./seed/seed build [path] [-o output] [compiler flags]
./seed/seed run [path] [-- program arguments]
./seed/seed test [path] [compiler flags]
./seed/seed format [--check] [file.sd]
./seed/seed fmt [--check] [file.sd]  # short alias
./seed/seed fmt --write directory/
./seed/seed fmt --check --json [--output file] [file.sd|directory]

With `--check`, a directory recursively checks its `.sd` files; `.git`,
`target`, and `.seed` directories are ignored.
With `--json`, the formatter emits `seed.format.v1` with per-file status and
stable checked/needs-format/error counts; a non-zero status still includes the
complete JSON result.
./seed/seed new path [--name package_name]
./seed/seed pkg resolve|add|update|prune ...
./seed/seed install [path|package] [--profile release] [--system] [--prefix path] [--dry-run]
./seed/seed uninstall package [--system] [--prefix path] [--dry-run]
./seed/seed doc [path] [--json] [--check-links] [--output file]
./seed/seed lint [--json] [--deny-warnings] [--cache dir] [--no-cache] [--fix] [--output file] [path]
./seed/seed explain [--json] CODE
./seed/seed explain [--json] --list
./seed/seed lsp
./seed/seed repl [--json]
./seed/seed publish --check [--json] [path]
./seed/seed publish --check [path]
./seed/seed mobile android <setup|sign|build|stage|check|package|run|doctor>

check is a narrow direct source check that runs the full semantic pipeline, including every mandatory safety proof (ownership, provenance, effects, cleanup, loans, concurrency); reusing a cached HIR does not bypass any of them. check --deep adds only the didactic lint pass after the semantic check passes; it never replaces a proof. It resolves relative source/interface imports and local manifest path dependencies; it does not first compile a missing bare repository package. Use build or run when validating package integration that depends on automatic core-library staging.

check --report report.json writes a deterministic seed.check-report.v1 summary with the source, file/LOC counts, deep-mode flag, result, total elapsed time, and separate source-resolution, semantic-check, and (when requested) deep-lint phases. On POSIX hosts it also records peak_child_rss_kib for the compiler/lint subprocesses. If deep lint fails, the report marks both the deep-lint phase and overall result as fail and includes its error/warning counts. The report also includes internal compiler phase timings and peak RSS when the LLVM profiler is available. --max-ms and --max-rss-kib add explicit CI budgets; they are post-run checks: the compiler subprocess runs to completion, then the tooling judges wall time (from tool start) and child peak RSS against the ceilings. Exceeded budgets are recorded in the report and fail the command after emitting structured output (budgets.violations plus the distinct check budget exceeded error). --max-files and --max-loc apply the same policy to reachable source size.

Compiler failures come in two classes. A semantic failure is a program violation reported as specific diagnostics (codes such as S001, H022) with spans, notes and help. An infrastructure failure — compiler out-of-memory or a toolchain contract violation — is reported as error[I-code]: detail (I900 fallback) or the generic "compiler ran out of memory or violated an infrastructure contract" message, with no output artifact published. Neither is the program-side alloc_error, which is a recoverable runtime result type inside generated programs (see the ownership chapter).

check --json emits the compiler's machine-readable diagnostics plus the checked source and a stable summary with error/warning counts. It can be combined with --deep to include the seed.lint.v1 deep-lint payload; add --deny-warnings to make compiler and deep-lint warnings fail the check while still emitting the complete JSON envelope. --output file writes that JSON atomically and keeps stdout clean, including when the check fails.

doc --json emits a seed.doc.v1 documentation index containing public declarations, rendered Markdown, interface-only dependencies, and structured link diagnostics. doc --check-links turns unresolved links into a failing status. lint --deny-warnings similarly turns lint warnings into a failing status without changing the default warning behavior; lint --output file writes the JSON or text result atomically, including on a failing lint run. Lint results are reused from a content-addressed per-source cache under .seed/cache/lint by default; --cache dir or SEED_LINT_CACHE overrides it, and --no-cache forces a cold run. --prune-days N removes old cache entries and reports the count. SEED_BUILD_JOBS controls bounded parallel source checks while preserving diagnostic order. Cache entries use the seed.lint-cache.v1 envelope; malformed or older entries are rebuilt and reported as cache.invalidated. Documentation-only L001 diagnostics are cached independently per public declaration, so unrelated edits can reuse them; semantic checking remains whole-source. --fix applies only safe documentation-comment fixes and disables cache reuse for that run.

The compiler's verified-HIR cache can be maintained explicitly with --cache-prune-days N together with --cache-dir dir. It removes only aged regular .hci identity files from that directory; unrelated files, symlinks, temporary publications, and recent entries are preserved. Pruning is advisory and never bypasses compilation or safety verification.

explain --json CODE emits a seed.explain.v1 record containing the title, meaning, invalid example, and repair guidance for a diagnostic code. Without --json, it retains the human-oriented long-form explanation. explain --list enumerates the available catalog; JSON mode emits seed.explain-catalog.v1 with the complete structured records.

repl is a check-only line-oriented session. Each entered line is validated as part of the current fn main region before being accepted. Multiline regions use :begin and :end transactionally; :check, :show, :reset, :undo, :help, and :quit manage the session. :show prints only the accepted region, excluding any open transaction. It does not execute code or weaken ownership rules. LSP completion follows the current grammar, including return, loop, break, and continue; removed legacy spellings are not suggested. repl --json emits one seed.repl-event.v1 JSON object per event for editor and automation consumers, including pending transaction lines and session exit. check --max-files N and check --max-loc N reject projects that exceed explicit reachable-source scale budgets and record violations in the check report and JSON diagnostics envelope. The JSON envelope also exposes observed metrics for reachable files, LOC, elapsed milliseconds, and peak child RSS. The result field is explicitly pass or fail, including budget failures. With --report, JSON mode also embeds the complete phase report while writing the requested report file atomically.

publish --check --json emits seed.publish-check.v1 with the package, version, and pass/fail result for CI integrations; --output file writes it atomically. Validation failures still return a non-zero status after emitting the structured result. Failure envelopes include stable error_code and the resolved project, so consumers do not need to parse human error text. Known classes include PUBLISH_MANIFEST, PUBLISH_LICENSE, PUBLISH_DEPENDENCY, PUBLISH_PLATFORM, and PUBLISH_FFI_INSTALL.

build performs incremental compilation with automatic dependency resolution. Bare package imports (e.g. use "vec") are staged into an isolated target/profile store. A second build reuses matching .sdi/native artifacts. test forwards trailing compiler flags, including repeated --link-object and --link-library inputs, to every test binary. This is required for library tests whose safe Seed surface is backed by an external native ABI.

install compiles the package in release mode and installs it system-wide: executables to ~/.local/bin (or /usr/local/bin with --system), dependency generations to ~/.local/lib/seed/generations/, and interfaces to ~/.local/share/seed/interfaces/. Use --profile debug for debug builds and --prefix /custom/path for custom install locations.

uninstall removes a previously installed package and its dependency generations. Both install and uninstall support --dry-run for preview. The transactional installed-package record includes target, profile, dependency-generation identity, compiler checksum, installed-artifact checksum, source checksum and lock checksum so a deployed application can be traced to its exact build inputs.

Remote publication is separate; seed publish --check validates package and lock integrity locally without running unrestricted scripts. Compiled packages use target-qualified .sdi plus --install-library/--install-interface. Packages declaring extern fn must also provide INSTALL.md describing the required native setup for their declared platforms; the file must be readable UTF-8 and non-empty. Local monorepos can keep multiple independently versioned packages connected by relative path dependencies. Their tree checksums drive rebuilds; ordinary local development does not require a Git repository, tag, or publication for each library.

The launcher reads [build].profile = "debug" | "release" | "release-small"; legacy release = true maps to release. An explicit CLI profile overrides the manifest. seed install defaults to release regardless of the manifest unless --profile is explicit. Applications default to src/main.sd; an optional safe relative [package].entry selects another source within the package. Bare package imports resolve to matching installed artifacts first; otherwise they are compiled with the application profile into an isolated target/profile store. Hosted builds link them dynamically by default. A second build reuses matching .sdi/native artifacts; private dynamic-library changes do not rebuild consumers, while public .sdi or ABI changes do.

User installation places executables in ~/.local/bin, immutable dependency generations in ~/.local/lib/seed, and versioned interface mirrors under ~/.local/share/seed/interfaces. --system uses the platform system scope (/usr/local on Unix); --prefix overrides the root. Installation and removal are ownership-recorded and atomic, and no command silently edits shell startup files or loader environment variables.

Operators

Binary precedence from highest to lowest:

  1. multiplicative (*, /, %)
  2. additive (+, -)
  3. shifts (<<, >>)
  4. comparisons (<, <=, >, >=)
  5. equality (==, !=)
  6. bitwise AND (&), XOR (^), OR (|)
  7. logical AND (&&)
  8. logical OR (||)

&& and || short-circuit: a && b evaluates b only when a is true, and a || b evaluates b only when a is false. Both operands must be bool and the result is bool. Short-circuiting also applies to constant folding, so false && expensive() and true || expensive() are valid constant expressions even when expensive() is not constant.

Direct clean compiler

The executable is compiler/llvm/build/seed. Its authoritative flag list is seed --help.

# Source processing
seed --check source.sd
seed --format|--format-check|--format-write source.sd

# Code generation
seed --emit-llvm out.ll source.sd
seed --emit-object out.o source.sd
seed --emit-binary out source.sd
seed --emit-shared library.so source.sd
seed --emit-static-library library.a source.sd
seed --emit-raw out.bin source.sd
seed --run source.sd -- arguments

# Interface inspection
seed --interface-print file.sdi
seed --interface-hash file.sdi
seed --emit-interface out.sdi source.sd

# Library management
seed --install-library artifact --install-interface file.sdi --package name [--seed-home path]

# Debug dumps
seed --lex source.sd
seed --ast-print source.sd
seed --types-print source.sd
seed --symbols-print source.sd
seed --hir-print source.sd
seed --safety-print source.sd
seed --mir-print source.sd
seed --layout-print source.sd
seed --abi-print source.sd
seed --low-mir-print source.sd
seed --llvm-print source.sd

# Build options
seed --release source.sd
seed --release-small source.sd
seed --thin-lto source.sd
seed --pgo-generate dir source.sd
seed --pgo-use profile source.sd
seed --profile-print source.sd
seed --target target source.sd
seed --runtime linux-native|linux-libc source.sd
seed --runtime-profile profile source.sd
seed --runtime-feature feature source.sd
seed --static source.sd
seed --static=all source.sd
seed --sysroot path source.sd
seed --soname name source.sd
seed --entry symbol source.sd
seed --linker-script path source.sd
seed --code-model model source.sd
seed --relocation-model model source.sd
seed --no-red-zone source.sd
seed --link-object file.o source.sd
seed --link-library lib.so|lib.a|package source.sd
seed --cache-dir path source.sd
seed --seed-home path source.sd
seed --package name source.sd

# Diagnostics
seed --diagnostics human|json source.sd
seed --version
seed --help

Debug/inspection modes include token, AST, types, symbols, HIR, safety, MIR, layout, ABI, low-MIR, and LLVM printing. Target/runtime options include --target, --runtime-profile, --runtime-feature, --runtime, --sysroot, entry/linker-script controls, and separate object/library linkage.

--release uses seed-safe-o2-v1; --release-small uses seed-safe-oz-v1. Both are safe. ThinLTO and PGO are explicit release-only options; static linkage is independent. There is no operational --unchecked or --fast-unsafe profile.

Target status

Object support and end-to-end hosted support are separate claims. Linux x86-64/AArch64 are the closed hosted paths. Freestanding x86-64/AArch64/RISC-V64 have boot evidence at their declared levels. Windows x86-64 has PE/runtime/DLL/ task/network evidence under required Wine. macOS x86-64 and arm64 have explicit-SDK executable/dylib/static linking, runtime and pthread scheduling. Native Intel x86-64 execution remains; arm64 is cross-link-only. See compiler/llvm/tests/GATE14_TARGET_MATRIX.tsv.

On an Intel Mac, run SEED_MACOS_RUNNER_POLICY=required make -C seed/compiler/llvm gate14-macos-x86_64-test. The script discovers the SDK with xcrun and uses Apple's ld unless explicitly overridden.