- Larva
- Cómo funciona Larva
- El back end x86-64
Esta página aún no está traducida al español. Leer la versión en inglés
The x86-64 back end
Larva's own code generator for x86-64 Windows - a baseline compiler with no register allocator, Win64 calls and unwind info, SEH exceptions, GC safepoints and stack maps, COFF objects and in-memory code.
En esta página
- When it is used
- Code generation
- Frames and registers
- Value shapes
- Calling convention
- Statements and checks
- Wide integers and floats
- Garbage collection
- Safepoints
- Stack roots
- Barriers, pins, counting
- Exceptions
- Reading the generated code
- The object file
- In-memory code
- Compile time
- Run time
- Limits compared with LLVM
Larva.X64 turns the IR straight into x86-64 machine code, without LLVM. It is a baseline compiler: every local lives in a stack slot, and each statement is evaluated through a few scratch registers. It needs no register allocator and no optimizer, so it compiles quickly, and every value has an address at every call, which is what a garbage collector needs to find its roots.
LinkResult / IrModule (one program, no imports)
│ Monomorphizer.Run(…, GenericsMode.Specialize)
│ Compilable.Stub (functions over unsupported types)
▼
X64Codegen.Generate ── one FunctionCompiler per function, in parallel
│ (own Assembler: code, labels, relocations,
│ unwind info, scope table, stack roots)
▼ laid out at 16-byte boundaries
X64Program (code, functions, relocations, static data)
├──► CoffWriter ─► .obj ─► MSVC link + larva_rt ─► .exe / .dll
└──► X64Image ─► executable memory in this process
| File | What it holds |
|---|---|
X64Backend |
X64Backend.Compile (verify, specialize generics, generate), X64Options, X64Result, X64Program (code, functions, relocations, data; ToObject, EmitObject, Load) |
X64Codegen |
the code generator: value shapes, unwind info, and FunctionCompiler (frames, statements, terminators, calls, exceptions, wide numbers, places, ownership, pins, polls) |
Assembler |
an x86-64 encoder for the subset the code generator emits: Reg, Xmm, Mem, Cond, Label, Relocation |
CoffWriter |
the COFF object: .text, .xdata, .pdata, .llvm_stackmaps, .rdata, .data, symbols and relocations |
X64Image |
loads an X64Program into the running process, as a linker and loader would |
When it is used
| Where | How |
|---|---|
larva build --backend x64 |
An executable or DLL, linked with the C runtime by MSVC like an LLVM build. A project selects it with backend: x64 in its BUILD chunk. |
larva run --backend x64 |
The same, then runs it. |
larva native --backend x64 -o out.obj <objects>… |
A COFF object from linked .lvo objects (--exe links an executable). |
Binding profiles (larva bind --profile) |
On Windows x64 the profile program is compiled with this back end and run from memory (below). |
| The test suites | Every language feature runs "in LVM, x64 and LLVM" and must give the same output, in memory and as executables. |
Why it exists: compile speed. LLVM at -O3 spends most of a build optimizing and emitting; this back end spends tens of milliseconds generating code. The design document measured a whole build of a small program at about 1 s with LLVM O3 (optimize 317 ms, emit 316 ms, link ~200 ms) against 365 ms in total with the x64 back end. It is also the fastest way to get machine code into memory without loading LLVM, and an independent second implementation of every IR feature, which keeps the LLVM back end and the VM honest.
It is not for fast programs: see limits.
Only Windows x64 is targeted (COFF, Win64 calling convention, SEH). Linux and macOS builds use LLVM.
Code generation
X64Codegen.Generate first leaves out what it cannot compile: shared generic functions (native code always specializes, GenericsMode.Specialize), declarations (defined in another native image and called by symbol), and functions over a type the back end rejects (X64Result.Skipped; since Restorations 12 and 13 every primitive is supported, so this is empty in practice). Then each function compiles into an Assembler of its own, on several threads (Parallel.For); the pieces are laid out in order at 16-byte boundaries with int3 padding. Code inside a function is position independent (branches are relative), so only calls and RIP-relative loads need relocations.
Frames and registers
Every function has the same prologue, which its unwind info describes:
push rbp
mov rbp, rsp
sub rsp, N ; N patched once the frame is known, 16-aligned
Below rbp come, in order of first use: one slot per IR local (aligned to 8, up to 16), one byte per drop flag, one 8-byte pin slot per borrow inside a moving gc object, the exception slots, and scratch slots reused statement by statement (Temp). Above the slots is the outgoing argument area (at least the 32 bytes of shadow space). The epilogue is lea rsp, [rbp]; pop rbp; ret.
Registers:
| Register | Use |
|---|---|
rbp |
frame pointer; every slot is [rbp − offset] |
rax, rcx, rdx |
evaluating a statement; rax holds results and addresses |
r8, r9 |
third and fourth arguments; high halves of 128-bit pairs |
r11 |
the owning gc object of the last address computed (for the write barrier) |
xmm0, xmm1 (to xmm5) |
floats; xmm0–xmm3 for float arguments |
Only volatile registers are used besides rbp, so the prologue saves nothing else and the unwind info stays fixed.
Value shapes
X64Codegen.Shape decides how each IR type is held:
| Kind | Types | Held as |
|---|---|---|
Unit |
() |
nothing |
Scalar |
bool, integers up to 64 bits, &T, gc T, box T, pointers, fn, T? |
canonical 64-bit value once loaded (sign- or zero-extended by the type) |
Float |
f16, f32, f64 |
xmm; f16 is converted to f32 with F16C (vcvtph2ps / vcvtps2ph) to compute |
Aggregate |
structs, fixed arrays | bytes in the frame |
Wide |
i128, u128, f128 (16 bytes); i256, u256, f256 (32 bytes) |
64-bit limbs in the frame, lowest first |
Calling convention
Calls between Larva functions and to C follow the Microsoft x64 convention:
- The first four arguments go in
rcx,rdx,r8,r9, orxmm0–xmm3by position; the rest on the stack above the shadow space. - Aggregates of 1, 2, 4 or 8 bytes travel in a register; larger ones by reference to a copy. Wide numbers always travel by reference.
- Results come back in
raxorxmm0; a result that does not fit comes back through a hidden pointer. - On entry,
StoreParameterscopies incoming arguments (registers or the caller's stack) into their local slots. - Runtime services (intrinsics) take their arguments in
rcx,rdx,r8,r9(anf64as its bits, aboolwidened) and return inrax. Atomics are runtime calls too. - Under reference counting,
gchandles are passed at +0: the caller keeps its reference for the call and the callee borrows it, as in the LLVM back end.
Statements and checks
Each statement loads its operands into scratch slots (Operand, Read), computes in rax/rcx/rdx or xmm0/xmm1, and stores the result to its place (Store, with a write barrier when needed). Array indexing is bounds-checked unless BoundsAnalysis proves the index in range (X64Options.BoundsCheckElimination, on by default; larva native --no-bce turns it off). Failed checks jump to out-of-line panics shared by the function, which call larva_panic(code, …): 1 for an index out of bounds, 2 for division by zero, 4 for a bad length, 5 for a null value.
Wide integers and floats
Restoration 12 in the design document made 128- and 256-bit integers work on all back ends:
- add, sub, and, or, xor, not, negation and the six comparisons run limb by limb, chaining the carry (the
movs in between leave the flags alone); - mul is the schoolbook product, keeping only the low limbs;
- division, remainder and shifts call the runtime:
larva_wide_udiv,urem,sdiv,srem,shl,lshr,ashrwith(a*, b*, result*, limbs).
Restoration 13 added f16, f128 and f256. f128 and f256 are wide values computed by the runtime's soft-float routines: larva_float_add, sub, mul, div, rem, larva_float_cmp(a*, b*, limbs, cond) and larva_float_conv for every conversion that involves a wide float. Negation flips the top bit. The routines exist twice, bit for bit equal: in larva_rt.c for executables and in the in-process runtime (NativeRuntime, Larva.Native) for code loaded into memory.
Garbage collection
The back end emits the same GC cooperation as the IL lowering, with the GC of each function's domain (CodeGcOf), and calls the same larva_* runtime.
Safepoints
A tracing GC can only collect at a safepoint. Loop headers poll, and so does the entry of a function that can be re-entered without a loop (Safepoints.EntryPolls). A poll is one load and a branch; the runtime is called only when its flag says a collection is due:
mov rax, qword ptr [__imp_larva_poll_pending]
mov eax, dword ptr [rax]
test rax, rax
je skip
call larva_poll
skip:
The flag is reached as an import of the runtime. Making safepoints a load and a branch instead of a runtime call is what brought a loop of 50 M struct method calls from 342 ms to 42 ms in the design document's early measurements (LLVM; the x64 back end uses the same poll). Loop back edges from exception handlers do not count as loops: a handler starts by reading the exception, and no collection may run before that.
Stack roots
Because every local is in its frame slot, a function's roots are simply the slots whose type may hold a tracked handle (StackRoots.MayHoldTracked): StackRoot(register 6 = rbp, offset, size). They are written to an .llvm_stackmaps section in the format LLVM emits (version 3; StackMapFormat in Larva.Native), so one runtime reader serves both back ends. Code loaded into memory registers them with the in-process runtime (NativeRuntime.RegisterCode), which scans them precisely. In executables, the C runtime walks the stack and registers conservatively, accepting interior pointers.
Barriers, pins, counting
| GC capability | Emitted code |
|---|---|
| Barriers (generational) | after a store of handles into a gc object (owner in r11), read the owner's age byte just before its payload; only if it is old (1), call larva_gc_wb(owner, slot, size) |
| Moving | a pin slot per borrow inside a gc object, nulled at entry, larva_pin / larva_unpin |
| Reference counting | larva_gc_retain / larva_gc_release; the last read of an unborrowed local transfers its reference (the IL's rule) |
| Regions | larva_region_enter, larva_region_exit, and larva_region_depth / larva_region_unwind on exception paths |
Allocation calls larva_gc_new (larva_gc_new_in for another domain, larva_gc_finalizer with a finalizer) and larva_gc_array; boxes and raw cells use larva_alloc and larva_free. With tracing, objects are created without a type descriptor and stay conservative: the collector scans them word by word and never moves them. When nothing in the program traces, objects and arrays carry a descriptor ({ i32 size, i32 stride, i32 flags, i16 handles, i16 cells, i32 offsets[] }, the same as the LLVM back end) and are created with larva_gc_alloc / larva_gc_array, so a counted object that dies releases what it holds.
Exceptions
Exceptions are Windows SEH with __C_specific_handler, as in the LLVM back end:
throwcallsRaiseException(0xE04C5641, EXCEPTION_NONCONTINUABLE, 2, [payload, type id]). The payload is a heap cell holding the value (larva_exception_cell); the type id is the same in every native image of the program (0 means any Larva exception). The code's low bytes spellLVA.- Every call, throw or resume site that can be caught or needs cleanup gets scope-table entries covering its
call, plus anopafter it so the return address falls inside the range. - One entry per catch clause, with a generated filter (
int filter(EXCEPTION_POINTERS*, frame), placed after the function) that checks the code and the type id and stores payload and type into the handling frame. - When the frame owns something, a last "any Larva exception" entry lands on cleanup code that releases what the frame owns, unpins, closes its regions and raises the exception again.
UNWIND_INFOusesrbpas the frame register; with scopes, it is followed by the handler RVA and the scope table, whose RVAs are fixups (UnwindFixup) filled when the image is laid out.
The exc program from the IL page (a thrower that throws its argument when negative, and a caller that catches an i32 and negates it), compiled with this back end. The nop after the call, the frees of the exception cell on each path, the landing that copies the matched payload, and the raise at the end are all visible:
larva native --backend x64 --entry exc::caller -o exc.obj exc.lvo
compiled 1 object(s) to exc.obj (obj, direct x86-64 back end, 2 functions; bounds checks: 0 kept, 0 eliminated)
dumpbin /nologo /disasm exc.obj
…
exc::caller:
0000000000000000: 55 push rbp
0000000000000001: 48 89 E5 mov rbp,rsp
0000000000000004: 48 81 EC 90 00 00 sub rsp,90h
00
000000000000000B: 89 4D F0 mov dword ptr [rbp-10h],ecx
000000000000000E: 31 C0 xor eax,eax
0000000000000010: 48 89 45 E0 mov qword ptr [rbp-20h],rax
0000000000000014: 48 8D 45 F0 lea rax,[rbp-10h]
0000000000000018: 48 63 48 00 movsxd rcx,dword ptr [rax]
000000000000001C: 48 89 4D A0 mov qword ptr [rbp-60h],rcx
0000000000000020: 48 8B 4D A0 mov rcx,qword ptr [rbp-60h]
0000000000000024: E8 00 00 00 00 call exc::thrower
0000000000000029: 90 nop
…
00000000000000C8: 48 8B 45 D0 mov rax,qword ptr [rbp-30h]
00000000000000CC: 48 8B 55 C8 mov rdx,qword ptr [rbp-38h]
00000000000000D0: 48 89 45 B0 mov qword ptr [rbp-50h],rax
00000000000000D4: 48 89 55 B8 mov qword ptr [rbp-48h],rdx
00000000000000D8: 48 B9 41 56 4C E0 mov rcx,0E04C5641h
00 00 00 00
00000000000000E2: 48 C7 C2 01 00 00 mov rdx,1
00
00000000000000E9: 49 C7 C0 02 00 00 mov r8,2
00
00000000000000F0: 4C 8D 4D B0 lea r9,[rbp-50h]
00000000000000F4: E8 00 00 00 00 call RaiseException
Reading the generated code
larva native writes LLVM's assembly with --emit asm, but this back end has no text output: it encodes machine code directly.
larva native --backend x64 --emit asm -o app.s geom.lvo app.lvo
larva: the x64 back end emits objects only (--emit obj, no Polly)
larva native --backend x64 -o app.obj geom.lvo app.lvo
compiled 2 object(s) to app.obj (obj, direct x86-64 back end, 8 functions; bounds checks: 0 kept, 0 eliminated)
A disassembler reads the object. The trace program from the IL page has a loop that allocates (churn); here it is with MSVC's dumpbin:
larva native --backend x64 --entry trace::keep -o trace.obj trace.lvo
compiled 1 object(s) to trace.obj (obj, direct x86-64 back end, 2 functions; bounds checks: 0 kept, 0 eliminated)
dumpbin /nologo /disasm trace.obj
…
trace::churn:
0000000000000100: 55 push rbp
0000000000000101: 48 89 E5 mov rbp,rsp
0000000000000104: 48 81 EC 70 00 00 sub rsp,70h
00
000000000000010B: 48 89 4D F0 mov qword ptr [rbp-10h],rcx
000000000000010F: 48 8B 05 00 00 00 mov rax,qword ptr [__imp_larva_poll_pending]
00
0000000000000116: 8B 40 00 mov eax,dword ptr [rax]
0000000000000119: 48 85 C0 test rax,rax
000000000000011C: 0F 84 05 00 00 00 je 0000000000000127
0000000000000122: E8 00 00 00 00 call larva_poll
0000000000000127: 31 C0 xor eax,eax
0000000000000129: 48 89 45 C0 mov qword ptr [rbp-40h],rax
000000000000012D: 48 8D 45 E8 lea rax,[rbp-18h]
0000000000000131: 48 89 C1 mov rcx,rax
0000000000000134: 48 8B 55 C0 mov rdx,qword ptr [rbp-40h]
0000000000000138: 48 89 51 00 mov qword ptr [rcx],rdx
000000000000013C: E9 00 00 00 00 jmp 0000000000000141
0000000000000141: 48 8B 05 00 00 00 mov rax,qword ptr [__imp_larva_poll_pending]
00
0000000000000148: 8B 40 00 mov eax,dword ptr [rax]
000000000000014B: 48 85 C0 test rax,rax
000000000000014E: 0F 84 05 00 00 00 je 0000000000000159
0000000000000154: E8 00 00 00 00 call larva_poll
0000000000000159: 48 8D 45 E8 lea rax,[rbp-18h]
000000000000015D: 48 8B 48 00 mov rcx,qword ptr [rax]
0000000000000161: 48 89 4D C0 mov qword ptr [rbp-40h],rcx
0000000000000165: 48 8D 45 F0 lea rax,[rbp-10h]
0000000000000169: 48 8B 48 00 mov rcx,qword ptr [rax]
000000000000016D: 48 89 4D B0 mov qword ptr [rbp-50h],rcx
0000000000000171: 48 8B 45 C0 mov rax,qword ptr [rbp-40h]
0000000000000175: 48 8B 4D B0 mov rcx,qword ptr [rbp-50h]
0000000000000179: 48 39 C8 cmp rax,rcx
000000000000017C: 0F 9C C0 setl al
…
000000000000020A: 48 C7 C1 08 00 00 mov rcx,8
00
0000000000000211: 48 C7 C2 04 00 00 mov rdx,4
00
0000000000000218: E8 00 00 00 00 call larva_gc_new
000000000000021D: 48 8B 4D C0 mov rcx,qword ptr [rbp-40h]
0000000000000221: 48 89 48 00 mov qword ptr [rax],rcx
…
000000000000026D: E9 CF FE FF FF jmp 0000000000000141
…
0000000000000292: 48 8B 45 F8 mov rax,qword ptr [rbp-8]
0000000000000296: 48 8D 65 00 lea rsp,[rbp]
000000000000029A: 5D pop rbp
000000000000029B: C3 ret
What the listing shows about the design:
- The function polls at entry and at the loop header (
0x141), the target of the back jump at0x26D. i < nloads both locals from their slots into scratch slots, then intoraxandrcx, compares, and stores theboolback to its slot: every value goes through memory.- A jump to the next block is still emitted (
jmp 0x141at0x13C): there is no layout or peephole pass. gc.newof the 8-bytePointislarva_gc_new(8, 4)(size, alignment), then the value is copied into the new object.- Calls and
__imp_loads are00 00 00 00placeholders: relocations the linker fills.
The object file
CoffWriter writes what link.exe or lld-link consume:
| Section | Contents |
|---|---|
.text |
the code; IMAGE_REL_AMD64_REL32 relocations for every call and RIP-relative load |
.xdata |
UNWIND_INFO per function, with __C_specific_handler and its scope table when it has handlers |
.pdata |
the function table: begin, end, unwind info (IMAGE_REL_AMD64_ADDR32NB) |
.llvm_stackmaps |
tracing modes only: the stack roots, with an IMAGE_REL_AMD64_ADDR64 to each function |
.rdata / .data |
static arrays (read-only, length header then elements, aligned to 16) and globals (writable, initial value) |
Larva functions are external symbols; runtime functions (larva_*, RaiseException, __C_specific_handler) are undefined externals for the linker. For trace.obj:
dumpbin /nologo /symbols trace.obj
…
006 00000000 SECT4 notype Static | .llvm_stackmaps
Section length A0, #relocs 2, #linenums 0, checksum 0
008 00000000 SECT1 notype () External | trace::keep
009 00000100 SECT1 notype () External | trace::churn
00A 00000000 UNDEF notype () External | __imp_larva_poll_pending
00B 00000000 UNDEF notype () External | larva_gc_new
00C 00000000 UNDEF notype () External | larva_poll
…
When larva build --backend x64 makes an executable (ExecutableBuilder), function symbols get the prefix larva. so they cannot clash with C names such as main; the entry wrappers are added; and the object is linked with the runtime larva_rt.c, compiled once by MSVC and kept in the toolchain cache (%LOCALAPPDATA%\Larva\toolchain). Executables link no C library, only kernel32.dll and ntdll.dll, with /NODEFAULTLIB and the runtime's own entry point ("Item 11" of the design document: a hello world is about 58 KB, and start-up dropped by about 12% without the C library). LARVA_MAP=1 also writes a linker .map for crash reports and profiles. See native builds and the runtime.
In-memory code
X64Program.Load() returns an X64Image: the program placed in the running process the way a linker and loader would.
- One
VirtualAllocblock (read, write, execute) holds the code, a 16-byte jump stub per external symbol, the unwind info, the function table and the static data. - Each stub is
jmp [rip + 0]followed by the 64-bit target, so calls reach the runtime with a 32-bit displacement however far it is. Targets come from the in-process runtime (NativeRuntime.Symbols);RaiseExceptioncomes fromkernel32.dlland__C_specific_handlerfromntdll.dll. An__imp_symbol (such as__imp_larva_poll_pending) stores the address of runtime data in the stub, where a RIP-relative load reaches it. - Relocations are resolved, unwind fixups filled, and the function table registered with
RtlAddFunctionTable, so the operating system can unwind through the code and SEH exceptions work. - The stack roots are registered with
NativeRuntime.RegisterCode, and the instruction cache is flushed. image.Address("name")gives a function's entry point; disposing the image deletes the function table, unregisters the code and frees the block.
Binding profiles use this path on Windows x64: compile the profile program, Load(), call the entry, reset the in-process runtime. The tests use it to run every linked program in memory next to the VM and the LLVM JIT.
The LLVM back end has its own in-memory path, Larva.Llvm/JitMemory: a memory manager for LLVM's MCJIT that places all sections of a module in one executable block (so its image base is the block start), registers the module's .pdata with RtlAddFunctionTable on finalization, and hands its .llvm_stackmaps to the same NativeRuntime. Without the function table, the operating system cannot unwind through JIT frames and any SEH exception crashes the process. Windows' CET shadow stack rejects exception continuations into such code unless they are registered, so hosts that run JIT code with exceptions opt out of CET compatibility.
Compile time
A small program (sum.ant below), built twice with each back end by ahead-of-time compiled larva, second runs:
larva build --timings --backend x64 -o sum-x64.exe sum.ant
sum-x64.exe (x64 back end, entry sum-x64::Demo.$Module.Main)
timings: System.Core (compiled library) 6 ms, frontend 102 ms, HIR link 10 ms, lowering 149 ms, IR checks 99 ms, IR link 50 ms, native (x64) 527 ms; total 943 ms
larva build --timings -o sum-llvm.exe sum.ant
sum-llvm.exe (llvm back end, O3, entry sum-llvm::Demo.$Module.Main)
timings: System.Core (compiled library) 8 ms, frontend 96 ms, HIR link 11 ms, lowering 134 ms, IR checks 90 ms, IR link 48 ms, native (llvm) 512 ms; total 899 ms
The native step includes linking with MSVC, which dominates both on a small program and varies a lot from run to run (the design document measured it at about 200 ms). LARVA_TIMINGS=1 splits it:
LARVA_TIMINGS=1 larva build --backend x64 -o sum-x64.exe sum.ant
…
[timing] type check: 314 ms
[timing] region check: 0 ms
[timing] x64: generate: 21 ms
[timing] link (runtime and linker): 651 ms
sum-x64.exe (x64 back end, entry sum-x64::Demo.$Module.Main)
LARVA_TIMINGS=1 larva build -o sum-llvm.exe sum.ant
…
[timing] monomorphize (specialize): 3 ms
[timing] llvm: build IR (8 functions): 39 ms
[timing] llvm: verify: 20 ms
[timing] llvm: optimize (O3): 205 ms
[timing] llvm: emit object: 216 ms
[timing] link (runtime and linker): 1.418 ms
sum-llvm.exe (llvm back end, O3, entry sum-llvm::Demo.$Module.Main)
Generating code takes 21 ms with this back end and about 480 ms with LLVM (build IR, verify, optimize, emit). The difference grows with the program: LLVM's time is in optimizing and emitting each function, which the x64 back end does not do.
Run time
import System;
into namespace Demo
{
Sum(i64 n) : i64
{
i64 total = 0;
loop (i64 i = 0; i < n; i++)
{
total += i;
}
return total;
}
Main()
{
Console.WriteLine(Sum(100000000));
}
}
Both executables print 4999999950000000. The x64 one took 0.9 to 1.6 s over three runs; the LLVM one 16 ms (after a first run of 230 ms), because LLVM keeps i and total in registers and optimizes the loop, while this back end loads and stores both from the frame on every iteration and polls at the loop header.
The design document's measurements, executables against the C runtime:
| Benchmark | LLVM -O3 |
x64 |
|---|---|---|
| Fib(32), recursive calls | ~0 ms | 1 ms |
| 50 M struct method calls | 42 ms | 1,503 ms |
| 50 M virtual calls, one object | 131 ms | 1,446 ms |
| 50 M virtual calls, two objects alternating | 782 ms | 2,212 ms |
| 10 M objects created | 186 ms | 643 ms |
| 20 k exceptions | 44 ms | 40 ms |
100 M additions through T : number |
8 ms | 1,246 ms |
20 M Sqrt, Floor, Ceiling, Truncate, Abs, Max |
199 ms | 3,790 ms |
Exceptions cost the same (both go through the operating system's unwinder); calls and arithmetic in loops are where the baseline compiler pays. The last row also shows why the IR is cleaned up before any back end runs: the monomorphizer's constant folding and copy forwarding made generic Floor<f64> the same as the f64 version, and without them x64 was 2.75 times slower still.
Limits compared with LLVM
| x64 back end | LLVM back end | |
|---|---|---|
| Registers | none allocated: every local in its slot, scratch through memory | full register allocation |
| Optimization | none (no inlining, no loop or layout passes; jumps to the next block remain) | -O0 … -O3, inlining, vectorization, --cpu |
| Allocation | runtime call for every object | small objects and arrays allocated inline from the size class's free list |
| Generics | specialized (as LLVM) | specialized |
| Platforms | Windows x64 only (COFF, Win64, SEH) | Windows, Linux, macOS (x64 and arm64), WebAssembly |
| Output | COFF object (or memory); no --emit asm / ll, no Polly |
object, assembly, LLVM IR |
| Reference counting | +0 handles; borrowed field reads not yet | +0 handles and borrowed field reads (FieldBorrows) |
| Stack roots in executables | frame slots in .llvm_stackmaps; the C runtime scans conservatively |
the same |
| Compile time | tens of ms to generate | hundreds of ms or more |
The design document lists register allocation as what this back end would need to be fast. Planned in the design document, not implemented: borrowed field reads under reference counting in x64.
Its role is the opposite of LLVM's: quick builds while developing, a way to get machine code into memory without loading LLVM, and a second implementation that every feature must agree with. For shipping, build with LLVM (the default).
Editar esta página en GitHub Reportar un problema