Skip to main content
asm blocks and asm functions are not supported on Windows. The MSVC build path does not wire up NASM, so any source file containing asm will fail to produce a working binary on Windows. See Compiler Status and the Windows section of the installation guide.
C△ supports inline assembly through the asm keyword. There are two forms:
  • asm functions — named, callable from C△ code with parameters and a return type. Must end with return unless the return type is void.
  • Anonymous asm {} blocks — bare blocks embedded in surrounding code. Must end with an explicit exit (typically a sys_exit syscall), because control would otherwise fall through into the surrounding C△ code with the CPU still mid-assembly.
Each asm block is compiled to a separate .asm file, assembled with NASM, and linked into the final binary by GCC — fully transparent to the rest of your program. You write assembly with the same function-call interface as any other C△ function, using native C△ type declarations for parameters and return for the exit path.

Basic Syntax

Define an asm function by opening a block with the asm keyword, a function name, a parameter list, and the syntax and section directives.
Each element of the block has a specific role:
  • Function name — becomes the global label in the generated .asm file and the callable symbol linked into your binary.
  • Parameters — declared using C△ type syntax (e.g., int a). You do not use assembler directives like %define or equ for parameters.
  • syntax — declares the target architecture and platform. Accepted values are x86_64_linux, x86_64_elf, and arm64_macho.
  • section .text — mandatory for x86_64 NASM targets. Use .section __TEXT,__text for arm64_macho.
  • return; — emits a ret instruction. By itself, return; returns whatever value is currently in the platform return register (rax on x86_64, x0 on ARM64) — you do not need to declare a return type or write return expr; if your assembly has already placed the result there. Use return expr; when you want the compiler to move a C△-level expression into the return register for you before returning. Required for every non-void asm function; a void asm function may omit it (the compiler emits a bare ret).
Variable declarations inside asm blocks may omit semicolons — a newline terminates both declarations and instructions. This keeps the block readable alongside raw assembly mnemonics.

Example: Add Two Integers

The following asm function adds two integers and returns the result through the standard calling convention.
Call it from the rest of your C△ program exactly as you would any ordinary function:
The compiler generates a .asm file for addInts, assembles it with NASM, and links the resulting object file alongside the GCC-compiled C output.

Example: Raw Syscall

When you need direct kernel access, write the instructions by hand. Use mov and syscall (or svc on ARM64) as you would in standalone assembly.
exitProcess is declared void and terminates via sys_exit, so no return is needed. For any non-void asm function, return (or return expr;) is required.

Anonymous asm Blocks

Anonymous asm {} blocks can appear at file scope or nested inside any C△ scope — most commonly the body of a function, but also if/else, loops, or any other block. Variables declared inside the block (using C△ declarations like int counter = 0) are registered in the enclosing C△ scope, so the surrounding C△ code can read and write them by name as if they had been declared there directly.
An anonymous asm {} block must end with an explicit exit when it is the final thing the program runs — otherwise control falls through into the surrounding C△ code with the CPU still mid-assembly. Use the platform’s exit syscall — mov rax, 60; mov rdi, 0; syscall on x86_64 Linux or mov x16, 1; mov x0, #0; svc #0x80 on ARM64 macOS. When the block is embedded mid-scope (as above) and the surrounding C△ code is expected to keep running, end with a ret-free fallthrough is acceptable — just make sure the CPU state on exit is consistent with the surrounding C△ code’s expectations. Unlike asm functions, return is not valid here — anonymous blocks do not have a return frame.
For embedding raw assembly without defining a named function, use a bare asm {} block. The earlier example above shows a nested block; the example below shows a top-level block that exits the process directly.
Use anonymous blocks when you need to set up data labels, share locals with the surrounding C△ scope, or emit architecture-specific instructions that are not naturally expressed as a function call.

Syntax Targets

The syntaxdirective tells the compiler which architecture and platform to target when generating the .asm file and choosing the NASM output format.

Compilation Pipeline

The Hypotenuse Compiler processes asm blocks on a separate path from ordinary C△ code. Both paths converge at the GCC linker step to produce the final binary.
You do not need to invoke NASM or manage object files yourself. Pass your .ctri source to hypc and the entire pipeline runs automatically.

Rules

Each asm block is entirely opaque to the simulation pass. The compiler does not perform last-use analysis or autoremove tracking inside asm bodies. Manage any heap pointers you pass into assembly code manually, or perform a Robbery before the pointer enters the block.
Variables declared inside asm functions are registered both for import access from other modules and within the function’s own scope. Variables declared in bare asm {} blocks are registered in the enclosing C△ scope — whether that’s a function body, a control-flow block, or file scope — and can be referenced by name from the surrounding C△ code as if they had been declared there directly.
Do not use raw assembler data directives such as db, dw, or dd inside asm blocks. Represent data storage with C△ variable declarations instead. The compiler translates these into the correct NASM output for your target.
The text section directive is mandatory in every asm block. Omitting it causes a NASM assembly error at build time. Use section .text for x86_64 targets and .section __TEXT,__text for arm64_macho.
  • Use return; to emit ret — it returns whatever is in the platform return register (rax / x0), so a typed return annotation is optional when your assembly already populates it. Use return expr; to move a C△-level expression into the return register first. Do not write ret directly.
  • Each asm block produces its own .asm file. Name your asm functions clearly — the function name becomes the exported symbol linked into the final binary.