Ahead-of-time compilation#

@aot lowers a checked top-level local function through verified IR before the program runs. CPU entries produce C or Wasm, while target = "gpu" produces a typed resident-buffer kernel and either a native SPIR-V or browser WGSL artifact.

@aot
local function clamp(value: number, low: number, high: number): number
    if value < low then return low end
    if value > high then return high end
    return value
end

nupp check validates the target and the structural subset, so @aot on something the backend could not compile is an error rather than a surprise later, and a check never needs a C compiler. Build policy selects what a build does with a CPU result: off by default, emit-c to write C beside the build, and require to compile it into the project's own shared library and call it. Lua 5.1 applications have corresponding emit-wasm and require-wasm policies for pointer kernels and Lua-building entries.

Pure numeric and span bodies keep the small kernel ABI, and a body that constructs fresh Lua values uses the separate lua-builder ABI. GPU entries replace the function with a checked compile, bind, and dispatch surface. Nothing in the source names an ABI, a lane, a mask, or a vector width.

Const-specialized families#

An @aot function may use scalar const binders when checked direct calls close their carrier parameters:

@aot(vectorize = false)
local function doubled<const N: integer>(value: number, count: N): number
    local answer = value
    for _ = 1, count as integer do answer = answer * 2.0 end
    return answer
end

local result = doubled(5.0, 3)

The build collects closed applications across the module graph, emits a private native body per admitted body class, and removes the carrier from that private ABI. Constant folding and bounded unrolling run before lane selection. Checked same- and cross-module calls use the private entry; the public function value dispatches tuples that were included in the deliverable and reports an unmatched tuple instead of silently falling back from an AOT-required build.

One module may emit eight logical const body classes before target tiers multiply them into physical entries. Calls with the same semantic key deduplicate, and keys with the same proven body and ABI coalesce. Exceeding the cap is a build error for a required AOT family and names its demanding call sites. With AOT off, the generic Lua body remains available; at -O1 and above it may still be specialized by OPT-8.

Annotation guarantees#

@aot promises three properties.

The body compiles once, ahead of time, with an optimizing compiler behind it. LuaJIT is a tracing JIT: it compiles what it observes, it gives up on shapes it cannot record, and a hot loop that aborts a trace runs interpreted however hot it gets.

The body's numeric meaning is pinned. Ordinary binary64 Nupp arithmetic is neither contracted nor reassociated, so an AOT function's answers are a property of what was written rather than of the target that compiled it. Explicit wrapping integer operations may be reassociated because their modular answer does not depend on grouping. Ask for a relaxation per function with @relax.

And a body that is one map loop over spans may be lowered lane-parallel. That is the largest single win where it applies, and the compiler decides whether it applies.

Dive deeper

The annotation is a contract over ordinary Nupp rather than a restricted sublanguage: the body uses the same parser, type system, operators, and diagnostics, so removing the annotation changes performance and artifacts but never the source-level result. There is no silent per-function fallback, because a contract that degrades quietly is a comment.

See NEP 9 for more information.

Next pages#

Each page owns one part of the AOT pipeline.

  • CPU kernels covers inspection, generated C, calls, and representative measurement.
  • Lua values covers table and string construction through the VM-rooted builder ABI.
  • Vectorization covers automatic lanes, explicit SIMD, and target feature tiers.
  • Numeric semantics covers arithmetic guarantees and verification.
  • Builds and artifacts covers policies, cross builds, caches, and shipping.
  • Wasm applications covers Lua 5.1 hosts and Wasm side modules.
  • GPU compute covers native resident buffers, workgroup kernels, and browser WebGPU.

FAQ#

Does @aot change what a function answers?#

Only through @relax. Removing @aot, or building the same source under aot = "off", changes performance and artifacts and never the result, which is what the differentials in Verification check. @relax is the one annotation that changes the answer, and it says so per function.

Why did my loop compile but run one iteration at a time?#

Either the shape is outside what lane lowering admits, or the loop does too little arithmetic per byte it touches to pay for assembling the vectors. nupp aot --check exits 1 for the first case and names the construct; see Admitted loop shape.

Does a project need a C compiler?#

Only under aot = "require", emit-wasm, or require-wasm. off is the default and nupp check never compiles C, so validating @aot source needs no toolchain at all. See Accepting a C compiler and Wasm AOT applications.