Inline Assembly
Inline assembly is Sec's explicit escape hatch for machine operations that cannot be expressed adequately through ordinary Sec code or a compiler/platform-known primitive. It allows real target instructions to appear inside Sec source while keeping the compiler responsible for the surrounding type, ownership, borrowing, effect, stack, target and control-flow model.
Inline assembly is deliberately narrow. It is not a second programming language inside Sec and it is not a way to bypass the compiler. The assembly text describes the target instructions; the structured Sec contract around it tells the compiler what crosses the boundary and what machine state may change.
unsafe {
asm {
"syscall"
inputs:
rax(number)
outputs:
rax(result)
clobbers:
rcx
r11
memory
}
}
Purpose
Inline assembly is appropriate for operations such as:
- system-call and supervisor-call instructions;
- special CPU instructions without a normal Sec primitive;
- target-specific startup or runtime-free platform glue;
- low-level hardware operations where a canonical platform primitive does not yet exist;
- ABI adapters and carefully constrained machine-state operations.
When a normal Sec operation or compiler-known platform primitive already expresses the required semantics, that form is preferred. A canonical primitive can expose stronger machine-independent guarantees than opaque user assembly can safely claim.
Core Principle
The compiler continues to own the surrounding semantics:
- type checking;
- unsafe obligations;
- ownership and borrowing;
- effects;
- control flow and cleanup;
- stack accounting;
- target and execution-context validation;
- interrupt restrictions;
- link dependencies and diagnostics.
Where the compiler does not have a declared semantic contract, it remains conservative. It does not infer correctness-critical properties merely by recognizing a mnemonic.
Target Machine Assembly
The template inside asm is real assembly for the selected build target.
Register names, instruction names and addressing forms therefore come from the target's
canonical assembler dialect.
An asm block does not select its own architecture. It is validated against the already
resolved CompilationPlan.
// On an amd64 target:
asm {
"syscall"
...
}
The same source cannot silently reinterpret syscall, rax or
another target concept according to the compiler host. Cross compilation always uses
the selected target.
Not MLIR or LLVM IR
Inline assembly is machine assembly. It is not source syntax for injecting Sec MLIR or LLVM IR.
Sec source asm
!= Sec MLIR injection
!= LLVM IR injection
An LLVM-based backend may eventually materialize a Sec asm operation through LLVM's inline-assembly facilities, but LLVM constraint strings and LLVM-specific flags are backend details. They are not the Sec source contract.
Unsafe Boundary
Inline assembly is an unsafe operation and therefore requires an explicit
unsafe context.
Declaring the surrounding function as unsafe fn does not make its body an
implicit unsafe context.
unsafe fn ReadMachineState() uint64 {
unsafe {
asm {
"..."
outputs:
rax(result)
}
}
return result
}
Unsafe does not disable type checking, borrowing, effects, stack analysis, ISR analysis, target validation or cleanup requirements. It accepts the specific proof obligations of the machine boundary; it does not mean “trust everything”.
Structured asm Block
A Sec asm operation consists conceptually of two parts:
target assembly template
+
compiler-visible Sec contract
The contract may describe:
- inputs and outputs;
- fixed registers or other target operand constraints;
- clobbers;
- memory and external effects;
- control-flow behavior;
- stack effects;
- symbol dependencies;
- target, feature and execution-environment requirements.
The assembler template itself is not a complete compiler contract. Correctness-critical facts must be represented explicitly or supplied by a stronger compiler/platform-known primitive.
Inputs
An input contains an ordinary Sec expression. The expression is type-checked, evaluated exactly once before the asm operation, and then materialized according to the declared target operand constraint.
inputs:
rdi(argument)
The backend may choose how to materialize that value, but it must not duplicate the
evaluation of argument merely because register allocation requires movement.
Physical Constraints
In a fixed-register operand such as rax(number), rax is a
physical target constraint. It is not a Sec variable.
Targets may expose operand categories equivalent to:
- fixed register;
- register class;
- immediate;
- memory operand;
- address operand;
- target-defined machine operand class.
Constraint names resolve against the selected target. An x86-64 register name is invalid for an AArch64 compilation plan even if the compiler happens to run on x86-64.
Direct Operand Types
Sec 0.1 keeps the direct asm boundary intentionally small. Direct machine operands are limited to low-level representations that have an unambiguous machine meaning.
| Allowed category | Notes |
|---|---|
int8 / uint8 | Fixed-width integer operands. |
int16 / uint16 | Fixed-width integer operands. |
int32 / uint32 | Fixed-width integer operands. |
int64 / uint64 | Fixed-width integer operands. |
int128 / uint128 | Only when the target operand class supports that width. |
float32 / float64 | Only when the target operand class supports the representation. |
RawPtr[T] | When the target supports the corresponding raw address width and address space. |
High-level values such as strings, structs, unions, arrays, collections, references, interfaces, closures and shaped values do not cross the ordinary register asm boundary directly. They must be explicitly decomposed into the primitive representation the machine operation actually requires.
Exact Widths
A fixed-width asm operand requires a Sec value with the same logical bit width. Inline assembly performs no implicit integer widening, narrowing, sign extension or zero extension.
// Conceptual rule:
32-bit machine operand -> int32 or uint32
64-bit machine operand -> int64 or uint64
If a conversion is intended, perform it explicitly in Sec before crossing the asm boundary.
Target-sized int and uint are not direct fixed-width asm register
operands merely because their resolved size happens to match the current target.
Low-level ABI code should use the explicit fixed-width type required by the machine
contract.
RawPtr Operands
RawPtr[T] is the canonical raw-address operand when assembly needs an
address. Passing a raw pointer does not create a safe reference, establish ownership,
extend lifetime or prove that the addressed memory is valid.
Assembly also does not acquire hardware authority merely because a pointer value can be placed in a machine register. Address-space and execution-context requirements remain part of the selected platform contract.
Outputs
An output names a physical location whose final representation produces a new low-level Sec value after normal completion of the asm operation.
outputs:
rax(result)
The output is not available before the assembly executes. The physical register width does not by itself determine the Sec output type; the Sec contract must resolve the output type explicitly or unambiguously.
Ordinary asm outputs remain low-level. They do not directly fabricate safe references, owned resources, constrained nominal values or high-level aggregates. Construct or validate those values after returning to ordinary Sec semantics.
Ownership Boundary
Moving bits into or out of registers does not itself copy or transfer Sec ownership. Ordinary inline assembly has no implicit consuming operand semantics.
The same physical register may be both an input and an output:
inputs:
rax(number)
outputs:
rax(result)
This means the initial machine value comes from number and the final machine
value produces result. It does not mean that the Sec variable
number is mutated in place.
Clobbers
A clobber declares a machine resource that may be destroyed by the assembly operation and whose final value is not represented as a Sec output.
clobbers:
rcx
r11
Registers already declared as outputs do not also need to appear as clobbers merely because they are written. Conversely, raw inline assembly has no hidden target-specific default clobber set. If the actual instruction sequence destroys a compiler-visible machine resource, the contract must say so.
Target clobbers may also include condition flags, floating-point status, vector state or other target-defined machine resources.
Observable by Default
Ordinary asm is observable by default. If execution reaches a live asm
operation, the compiler must preserve that occurrence even when its outputs are unused.
This does not make the containing function or module globally live. An unreachable private function may still be removed according to normal link reachability.
Observable asm is also not a global optimizer barrier. Independent surrounding code may still move or optimize when data, memory, machine-state and ordering contracts allow it.
No asm volatile
Sec 0.1 does not define:
asm volatile
volatile asm
@volatile asm
The word volatile belongs to physical-storage access semantics. Inline asm
observability is a separate concept and is already the default for ordinary asm.
memory Clobber
The special memory clobber says that the operation may access memory whose
complete footprint is not otherwise described precisely.
clobbers:
memory
This forces conservative compiler memory reasoning around the operation. It does not mean Acquire, Release or SeqCst, and it does not emit a CPU fence, cache operation, DMA handoff or device-completion sequence.
memory is a compiler-visible unknown-memory effect,
not a hardware synchronization primitive.
Compiler vs Hardware Ordering
Compiler ordering and hardware ordering are distinct. An asm memory effect may prevent the compiler from reordering memory operations across the boundary while still emitting no physical fence instruction.
Likewise, hardware ordering and hardware completion are separate. A fence, read-back or target-specific completion instruction only provides a positive Sec guarantee when the owning platform contract states exactly what relation it establishes.
The compiler must not infer a completion guarantee merely because it recognizes a familiar instruction mnemonic.
Atomics and Synchronization
A machine instruction can be physically atomic without automatically becoming a proven Sec atomic operation. Raw assembly does not create a second memory model.
If Sec relies on an operation for synchronization, race freedom or a memory-order proof, the stronger semantics must come from a canonical Atomic or platform primitive contract. The same principle applies to interrupt masking: raw mask-manipulation instructions do not automatically establish a proven Sec critical section.
Control Flow
Local labels, loops and branches are allowed inside one asm operation when the target assembler supports them. That internal machine control flow must remain inside the asm boundary.
Ordinary inline assembly must not:
- jump from asm into an arbitrary Sec basic block;
- jump from Sec into an internal asm label;
- use a machine
retto return directly from the enclosing Sec function; - use an interrupt/exception-return instruction as a normal source-level ISR return.
A truly non-returning asm operation requires an explicit compiler-visible
NoReturn or equivalent semantic contract so that the Sec control-flow graph
becomes terminal at that point.
Stack and ABI
Ordinary asm executes inside the surrounding Sec function's resolved ABI. The compiler still owns the prologue, epilogue, frame layout, stack alignment, return convention and required callee-saved preservation.
On normal fallthrough, the stack pointer must be restored to the state required by the enclosing function. Listing the stack pointer as a clobber is not enough to authorize a persistent stack change.
Temporary stack usage is possible only when the compiler-visible contract can describe the required final delta, maximum additional stack consumption and alignment facts needed by stack analysis. Unknown or unbounded stack use cannot satisfy a context that requires a finite positive stack proof.
Permanent stack switching or naked machine entry/exit is not ordinary inline assembly. It requires a stronger compiler/platform contract.
Symbols and Relocations
Ordinary Sec calls remain preferred when normal call semantics are sufficient. When assembly must refer to a Sec, foreign or platform symbol, the dependency must remain compiler-visible so the linker can preserve reachability, relocation and ABI semantics.
Source assembly should not depend on final compiler-mangled symbol spelling as its canonical link model. Local labels inside the asm template remain assembler-local and do not become Sec declarations.
Assembler Directives
Inline assembly may use target-approved local directives and raw instruction encodings when the target contract allows them. It must not use ordinary inline asm to take over the compiler's object model or build system.
Forms equivalent to the following are not ordinary asm responsibilities:
.section
.global
.weak
.org
.include
.incbin
Compiler-owned debug and unwind metadata are similarly not ordinary source asm. If the program needs a global symbol, separate section, startup root or object dependency, use the language/project/linking mechanism that owns that concept.
Target Validation
The target assembler being able to encode an instruction is not sufficient proof that
the instruction is valid for the selected CompilationPlan.
Validation may additionally require:
- a CPU feature or ISA extension;
- a particular execution mode;
- target availability;
- platform authority;
- a compatible security state;
- a compatible ABI or environment contract.
Inline assembly and assembler directives cannot silently enable a CPU feature absent from the compilation plan.
Privilege and Authority
Unsafe is not privilege. A privileged instruction remains invalid when the current execution environment does not provide the required authority, even if the instruction is valid for the processor and encodable by the assembler.
ISA-valid != userspace-valid
unsafe != privileged
The same principle applies to hardware authority, address-space access and security modes. Assembly cannot create capabilities that the selected platform profile does not provide.
System Calls and Environment Transitions
Inline assembly may implement architectural transitions such as system calls, supervisor calls, hypercalls, firmware calls, secure-monitor calls, debug traps or target-defined RTOS kernel entries.
Such a transition is not an ordinary Sec function call. Its input locations, output locations, clobbers, memory effects, blocking behavior, privilege transition, fault behavior and raw error convention come from the platform contract.
One instruction can therefore have strong effects. Instruction count says nothing about whether an operation may block, perform I/O, mutate external state or terminate control flow.
Raw Results and Faults
Assembly transports machine-level values. A platform convention such as “negative
integer means errno” does not automatically produce a Sec Result.
A higher-level Sec or platform wrapper interprets the raw result and constructs
Result, Option or another domain type using normal Sec rules.
Likewise, page faults, bus faults, illegal instructions, alignment faults, privilege
faults and architectural traps do not automatically become ordinary
Err(...) propagation. Their behavior is defined by the target, runtime or
interrupt model.
Effects and Analysis
Inline assembly contributes effects to the normal Sec effect analysis. Depending on the declared or platform-known contract, those facts may include effects equivalent to:
MayPanic;MayAllocate;MayBlock;MaySuspend;MayIO;MayAccessVolatile;MayMutateExternalState;MayUseNondeterministicInput;- machine-state or target-specific trusted effects.
Raw asm does not become pure merely because the source contains no Sec-level function calls. A stronger pure contract requires an explicit compiler/core/platform-known semantic guarantee.
Inline Assembly in ISRs
Inline assembly remains subject to the complete ISR contract. Using asm inside an ISR
does not waive noPanic, noAlloc, noBlock, stack,
target-authority, race, deadlock or runtime-context requirements.
Raw assembly that manipulates interrupt masks also does not automatically prove the exclusion relation required by ISR or race analysis. Canonical platform primitives are preferred when the program needs positive masking, ordering or completion guarantees.
Cross Compilation
Inline assembly is interpreted for the target, never for the compiler host. If Sec is running on Linux/amd64 while building bare-metal Cortex-M4 code, the assembly dialect, register set, features, ABI and privilege model are those of the Cortex-M4 target.
Host assembler capabilities must never silently influence target semantics. The CLI and
LSP must resolve the same target facts from the same CompilationPlan.
Lowering Pipeline
Inline assembly remains a first-class semantic operation until all analyses that depend on its contract have consumed or preserved the required information.
Sec source asm
|
v
resolved inline-assembly operation
|
v
Semantic IR
|
v
Sec MLIR / target lowering
|
v
backend inline asm / target assembler
|
v
machine encoding
A backend must reject a valid Sec contract it cannot represent faithfully rather than silently weakening operand width, clobber, observability, control-flow, ordering, completion or symbol semantics.
Diagnostics and Tooling
Sec distinguishes three broad diagnostic classes:
- Sec contract errors — such as wrong operand width, forbidden operand type, missing unsafe context or illegal control flow;
- target assembler errors — such as an unknown mnemonic, invalid instruction form or illegal addressing mode;
- backend/materialization errors — where a valid Sec contract cannot be represented correctly by the selected backend.
Multiline assembler diagnostics should map back to the original Sec source line inside the asm template. User-facing diagnostics speak in Sec terms such as register, operand type, width, clobber, target, symbol and effect rather than exposing LLVM constraint syntax as the primary model.
Complete Example
The following illustrates the intended shape of a Linux/amd64 raw syscall wrapper using fixed-width machine ABI values. The wrapper remains unsafe because it exposes a raw execution-environment transition rather than a typed higher-level OS API.
#target(os: "linux", arch: "amd64")
unsafe fn RawSyscall1(number: uint64, arg1: uint64) int64 {
unsafe {
asm {
"syscall"
inputs:
rax(number)
rdi(arg1)
outputs:
rax(result)
clobbers:
rcx
r11
memory
}
}
return result
}
The source explicitly describes the fixed machine registers used by the ABI and the machine resources destroyed by the transition. The raw scalar result remains a raw scalar. Any Linux error decoding belongs in a higher-level wrapper.
Current Implementation Status
The inline-assembly rulebook is normative, while implementation is currently partial.
Implemented today:
- the parser recognizes asm statements and the structured asm block form;
- assembly template, inputs, outputs and clobbers are retained by the parser;
- semantic analysis requires an explicit unsafe context;
- asm input expressions are visited and output bindings are created in the current model;
- the legacy LLVM path can emit LLVM inline asm with observable/side-effect semantics;
- fixed-register inputs, one fixed-register output and explicit clobbers have initial lowering support;
- Linux/amd64 raw syscall code already exercises the structured form.
Important current mismatches with the normative model include:
- the legacy LLVM path currently supports only one output and hardcodes it as
i64; - current lowering coerces asm inputs to
i64instead of enforcing exact Sec operand widths; - pointer operands are not yet preserved as complete target-aware
RawPtrcontracts; - current lowering may add implicit
rcx/r11clobbers, which the normative model forbids; - the current frontend still requires an output in cases where instruction-only observable asm should be legal;
- target register classes, feature requirements, privilege, dialect, stack and full effect contracts are not yet modeled end-to-end through
CompilationPlan; - the complete first-class Semantic IR / Sec MLIR asm operation is still pending.
Best Practices
- Prefer ordinary Sec code or compiler/platform-known primitives whenever they express the operation correctly.
- Keep asm blocks small and give the compiler the most precise contract available.
- Use explicit fixed-width integer types at machine ABI boundaries.
- Use
RawPtr[T]for raw addresses rather than silently converting addresses to integers. - Declare every destroyed compiler-visible machine resource as a clobber unless it is already an output.
- Do not invent
asm volatile; ordinary Sec asm is already observable. - Do not treat
memoryas a CPU fence or synchronization primitive. - Do not rely on raw atomic instructions or interrupt masking as positive Sec synchronization proof.
- Do not return from a Sec function using a machine
retinside ordinary asm. - Do not permanently switch stacks inside ordinary inline assembly.
- Do not create sections, global symbols or hidden build dependencies through ordinary asm directives.
- Validate machine features and privilege against the selected target, not against the compiler host.
- Wrap raw syscall or firmware conventions in ordinary Sec APIs that construct typed errors and values.
Summary
- Inline assembly is an explicit unsafe target-machine escape hatch.
- It escapes Sec's instruction vocabulary, not Sec's compiler model.
- The assembly template uses the canonical dialect of the selected target.
- Sec inline assembly is neither MLIR nor LLVM IR source injection.
- An
unsafe fnstill needs an explicit unsafe context around asm. - Inputs are ordinary Sec expressions evaluated exactly once before the asm operation.
- Fixed registers and operand classes are physical target constraints, not Sec variables.
- Direct operand types are intentionally limited to fixed-width machine primitives and
RawPtr. - Inline assembly performs no implicit integer width conversion.
- Outputs produce new low-level Sec values after normal completion.
- Physical register movement does not implicitly create or transfer Sec ownership.
- Clobbers explicitly describe machine resources destroyed without a corresponding output.
- Ordinary asm is observable by default and Sec 0.1 has no
asm volatile. memoryis conservative compiler memory reasoning, not a hardware fence.- Compiler ordering, hardware ordering and hardware completion are separate concepts.
- Raw machine atomicity does not automatically create Sec synchronization proof.
- Local machine control flow may remain inside one asm operation, but ordinary asm cannot jump into or out of the Sec CFG.
- The compiler retains ownership of the surrounding function ABI and stack discipline.
- Inline assembly cannot create absent CPU features, privilege or platform authority.
- Raw syscall results and machine faults remain distinct from typed Sec
Resulthandling. - Inline assembly contributes normal Sec effects and remains subject to ISR, stack, race and deadlock analysis.
- Cross compilation validates assembly for the target, never for the compiler host.
- The normative rulebook is complete, while current implementation remains partial.