ilusm.dev

asm

Inline assembly and x86-64 JIT - build byte sequences, emit instructions, seal to executable memory, and call.

Load with: use asm

What this module does

asm lets you build x86-64 machine code from ilusm and execute it at runtime. You create a program buffer, emit instructions into it (mov, add, sub, and, or, xor, push, pop, ret, nop), then seal the buffer - which maps it into executable memory - and call it with up to 6 integer arguments.

Before sealing, the module checks that the host runtime has the required native capabilities: __native_mmap_rw, __native_jit_put, __native_mprotect_rx, and __native_call.

Calling convention follows SysV AMD64: arguments go in rdi, rsi, rdx, rcx, r8, r9. Return value in rax.

Quick example - add two numbers

use asm

# Build a function: rdi + rsi -> rax, ret
p = asmne()
asmmo1(p, "rax", "rdi")   # mov rax, rdi
asmad(p, "rax", "rsi")    # add rax, rsi
asmre(p)                   # ret

jit = asmse(p)             # seal to executable
result = asmca3(jit, 10, 32)  # call with 2 args
prn(result)                # 42

Functions

Setup and capability check

asmca()

Checks that the host runtime has all required JIT capabilities. Returns tru if checks pass, errors with the missing capability name if not. If __ilusm_caps is not set, assumes all capabilities are present.

asmne()

Creates a new empty program buffer. Returns a {b: buf} object you pass to all emit functions.

asmar()

Returns the SysV argument register order: ["rdi", "rsi", "rdx", "rcx", "r8", "r9"].

Register encoding

asmid(nm)

Returns the numeric register index for a register name. rax=0, rcx=1, rdx=2, rbx=3, rsp=4, rbp=5, rsi=6, rdi=7, r8r15=8–15. Errors on unknown names.

Raw byte emission

asmpu(p, xs)

Emits a list of raw bytes into the program buffer. Use this for opcodes not covered by the helper functions.

Register instructions (r64)

All operate on 64-bit registers. REX.W prefix is always emitted. REX.R/B are set automatically for registers r8–r15.

asmmo(p, opc, d, s)

Emits a ModRM instruction with the given opcode, destination register d, and source register s. Handles REX prefix computation.

asmmo1(p, d, s)

mov r64, r64 - copies source register into destination.

asmad(p, d, s)

add r64, r64 - adds source to destination.

asmsu(p, d, s)

sub r64, r64 - subtracts source from destination.

asmsn(p, d, s)

and r64, r64 - bitwise AND.

asmso(p, d, s)

or r64, r64 - bitwise OR.

asmsx(p, d, s)

xor r64, r64 - bitwise XOR. Passing the same register for both d and s zeroes it.

asmmv(p, d, imm)

mov r64, imm32 - moves a 32-bit immediate (sign-extended to 64 bits) into a register. The immediate is encoded little-endian.

asmpu1(p, r)

push r64 - pushes a register onto the stack.

asmpo(p, r)

pop r64 - pops the top of the stack into a register.

asmre(p)

ret - emits a return instruction (0xC3).

asmno(p)

nop - emits a no-op (0x90).

Buffer operations

asmca1(p, q)

Appends the bytes from program buffer q onto program buffer p.

asmby(p)

Returns the program bytes as a list of integers.

asmbi(p)

Returns the program bytes as a bin view.

Sealing and calling

asmse(p)

Seals the program buffer - allocates executable memory (mmap with RW, then mprotect to RX), writes the bytes in, and returns a JIT handle. Runs capability check first.

asmca2(r, a0, a1, a2, a3, a4, a5)

Calls a JIT handle with up to 6 integer arguments. Returns the int64 return value. Pad unused args with 0.

asmca3(r, a, b)

Calls a JIT handle with 2 arguments. Shorthand for asmca2(r, a, b, 0, 0, 0, 0).

asmca4(r)

Calls a JIT handle with no arguments. Shorthand for asmca2(r, 0, 0, 0, 0, 0, 0).

Example programs

asmtr()

Builds, seals, and returns a JIT function that zeroes rax and returns - always returns 0.

asmta()

Builds, seals, and returns a JIT function that adds rdi and rsi and returns the result in rax.

Notes

  • x86-64 only. The calling convention is SysV AMD64.
  • The host runtime must expose __native_mmap_rw, __native_jit_put, __native_mprotect_rx, and __native_call. asmca() checks these before sealing.
  • asmca is overloaded: asmca() is the capability check, asmca1 appends buffers, asmca2/asmca3/asmca4 call JIT handles.