qbe

    Download zip
    Author
    Version
    0.5.1
    License
    Apache-2.0
    Last updated
    12 hours ago
    Downloads
    15

    Dependencies

    #azhzx/qbe

    A MoonBit rewrite of QBE

    #Project Documentation

    #Project Overview

    qbe.mbt aims to port the core backend capabilities of Quick Backend (QBE) to the MoonBit ecosystem.

    Provides a lightweight compiler backend.

    Provides SSA intermediate representation, IL text parsing and output, instruction selection, register allocation, ABI processing.

    #Core Feature Scope

    Provides QBE-style SSA intermediate representation model, supporting functions, basic blocks, temporary variables, instructions, jumps, phi nodes, data segments, and type systems;

    Supports QBE IL text format parsing, output, and pretty printing for exchanging intermediate representations with upstream QBE toolchain or custom frontends;

    Provides unified compilation entry point

    Supports amd64 (System V, GAS output, Linux/macOS two styles)

    Supports WebAssembly (wasm32, WAT text output)

    Supports RISC-V 64 (rv64, GAS output)

    Supports basic backend pipeline

    Supports common IL instructions

    Provides debugging auxiliary modules

    Provides unified compilation entry point @qbe.compile / @qbe.compile_debug, covering IL parsing, SSA construction, register allocation, and assembly output;

    Provides WebAssembly compilation entry point @qbe.compile_wasm / @qbe.compile_wasm_debug, covering IL parsing, SSA construction, and WAT text output;

    Provides RISC-V compilation entry point @qbe.compile_rv64 / @qbe.compile_rv64_debug, covering IL parsing, SSA construction, RISC-V register allocation, and assembly output;

    Provides MoonBit unit/blackbox/whitebox tests, maintaining core regression tests (.ssa differential regression + moon test);

    Provides README examples covering IL parsing, SSA construction, register allocation, assembly output, and target architecture selection.

    #Quick Start

    @qbe.compile compiles an IL text to amd64 GAS assembly; @qbe.compile_debug returns dumps from each stage:

    ///|
    test {
    let src =
    #|export function w $add(w %a, w %b) {
    #|@start
    #| %s =w add %a, %b
    #| ret %s
    #|}
    #|
    match @qbe.compile(src) {
    Ok(assembly) => {
    assert_true(assembly.contains("addl"))
    assert_true(assembly.contains("add:"))
    }
    Err(_) => fail("compile failed")
    }
    match @qbe.compile_debug(src, "P") {
    Ok(dump) => assert_true(dump.contains("After parsing"))
    Err(_) => fail("compile failed")
    }
    }

    @qbe.compile_wasm compiles an IL text to WAT (WebAssembly Text) format:

    ///|
    test {
    let src =
    #|export function w $add(w %a, w %b) {
    #|@start
    #| %s =w add %a, %b
    #| ret %s
    #|}
    #|
    match @qbe.compile_wasm(src) {
    Ok(wat) => {
    assert_true(wat.contains("(func $add"))
    assert_true(wat.contains("i32.add"))
    }
    Err(_) => fail("wasm compile failed")
    }
    }

    @qbe.compile_rv64 compiles an IL text to RISC-V 64 GAS assembly:

    ///|
    test {
    let src =
    #|export function w $add(w %a, w %b) {
    #|@start
    #| %s =w add %a, %b
    #| ret %s
    #|}
    #|
    match @qbe.compile_rv64(src) {
    Ok(assembly) => {
    assert_true(assembly.contains("add:"))
    assert_true(assembly.contains("addw a0, a0, a1"))
    }
    Err(_) => fail("rv64 compile failed")
    }
    }

    #Technical Details

    #Package Structure and Compilation Pipeline

    MoonBit packages organized by compilation pipeline stages (see doc/):

    PhasePackageDescription
    Data StructurestypesSSA IR: Fn/Blk/Ins/Phi/Jump/Con/Tmp/Dat etc., shared by all backend packages
    UtilitiesutilError types, string interning (Interner), output, sorting
    LexinglexerIL text → token sequence, errors collected to err_msgs instead of exceptions
    ParsingparserToken sequence → Fn/Dat/Typ, supports type/data/function three top-level definitions
    CFG AnalysiscfgReverse postorder, predecessors, dominator tree, dominance frontiers, loop depth, alias analysis, jump simplification
    SSA ConstructionssaUse chains, memopt, phi insertion, block renaming, loadopt, copy propagation, validity checking
    Constant FoldingfoldDirectly evaluates instructions whose operands are all constants and replaces with references
    Wasm ABIabi_wasmWasm calling convention: Par/Arg→Nop, Call simplification
    Wasm Instruction Selectionisel_wasmWasm op mapping, address mode decomposition, CFG→structured control flow
    Wasm Assembly Outputemit_wasmWAT text format output
    ABI ProcessingabiSystem V AMD64 calling convention: parameter/return registers, stack spilling, vararg
    Instruction Selectioniselamd64 instruction patterns: immediates, address modes, division magic numbers, conditional jumps
    Liveness AnalysisliveBackward data flow to compute in/out, block boundary statistics nlive_w/nlive_d
    Register SpillingspillCost-based and loop-weighted spilling point selection, iterates to convergence
    Register AllocationregaBuilds interference graph from live sets, greedy coloring
    Assembly OutputemitRenders GAS assembly (Linux .L/macOS L, _ prefix)
    RISC-V ABIabi_rv64rv64 calling convention: A0–A7 / FA0–FA7 parameters and returns, aggregate type splitting
    RISC-V Instruction Selectionisel_rv64rv64 instruction mapping, compare+branch merging
    RISC-V Assembly Outputemit_rv64RISC-V GAS text output
    CLI Entrycmd/mainArgument parsing and file I/O (thin shell, calls @qbe facade, -t selects target)
    Library Entry.Unified compilation API compile / compile_debug and IR type re-exports

    Complete pipeline (run_passes in pipeline.mbt, encapsulated for library users in @qbe.compile):

    parse → fillrpo → fillpreds → filluse → memopt → filldom → fillfron → filllive(false) → phiins → renblk → filluse → ssacheck → fillloop → fillalias → loadopt → filluse → ssacheck → copy → filluse → fold → abi → fillpreds → filluse → isel → fillrpo → filllive → fillcost → spill → rega → fillrpo → simpljmp → fillrpo → fillpreds → emitfn

    Wasm pipeline (run_passes_wasm, encapsulated for library users in @qbe.compile_wasm):

    parse → fillrpo → fillpreds → filluse → memopt → filldom → fillfron → filllive(false) → phiins → renblk → filluse → ssacheck → fillloop → fillalias → loadopt → filluse → ssacheck → copy → filluse → fold → abi_wasm → fillpreds → filluse → isel_wasm → [skip spill/rega — wasm has no physical registers] → emit_wasm

    RISC-V pipeline (run_passes_rv64, encapsulated for library users in @qbe.compile_rv64):

    parse → fillrpo → fillpreds → filluse → memopt → filldom → fillfron → filllive(false) → phiins → renblk → filluse → ssacheck → fillloop → fillalias → loadopt → filluse → ssacheck → copy → filluse → fold → abi_rv64 → fillpreds → filluse → isel_rv64 → init_rv64_target() ← switch TargetCfg (register layout) → fillrpo → filllive → fillcost → spill → rega → fillrpo → simpljmp → fillrpo → fillpreds → emit_rv64

    #Intermediate Representation Design

    • SSA IR: Functions (Fn), basic blocks (Blk), temporary variables (Tmp), instructions (Ins) are all mutable structs, modified in place without producing copies; supports phi nodes and multiple jump forms (unconditional jump, conditional jump, integer/float conditional jump, 5 return types).
    • Opcodes: Op enum covers all 100+ QBE instructions (arithmetic, bitwise, shifts, comparisons, load/store, extensions/conversions, alloc, vararg, call and internal instructions Nop/Addr/Swap/Xcmp etc.), with OpInfo carrying operand properties and foldable markers.
    • Reference Types: Ref is an operand reference, unifying temporary variables (RTmp), constants (RCon), types (RType), stack slots (RSlot), call points (RCall), memory (RMem).
    • Bit Sets BSet: Compact bit sets implemented with Array[UInt64], used for liveness variable sets and register masks.
    • Register Numbers: RAX=1..RSP=16, XMM0=17..XMM15=32, RXX=0 means "no register".

    #Key Algorithms

    • SSA Construction: Based on dominance frontiers (fillfron) inserts phi nodes, block and variable renaming establishes SSA form, ssacheck performs validity checking.
    • Liveness Analysis: Backward data flow iterates to fixed point; gen_set built once and reused, only recomputing in/out.
    • Register Allocation: Spill first by cost (use/definition point count + 10^loop_depth loop weighting, word/double channels evaluated separately by NGPS=9/NFPS=15), then in rega builds interference graph from live sets and does greedy coloring; inconsistent registers at block boundaries get copy inserted for synchronization.
    • Instruction Selection: Does semantics-preserving strength reduction — folding immediates into instructions, combining add chains into [base + index*scale + offset] addressing, converting constant divisor division to magic number multiply-add-shift, converting comparison + jnz patterns to amd64 conditional jumps.
    • Memory Optimization: memopt eliminates redundant alloc/load/store; loadopt eliminates repeated loads from same address with no intervening store in same block; copy propagation merges equivalent temporary variables.

    #ABI and Target Support

    Supports three targets, selected with -t on command line (amd64_sysv default), with independent library API entry points:

    • amd64_sysv: abi phase replaces abstract Arg/Par/Ret* with concrete register/stack slot references; aggregate types follow System V rules for register vs memory; outputs two GAS styles (Linux .L / macOS L + _ prefix, selected with -G). Has complete 406-case differential regression.
    • wasm: abi_wasm phase replaces Par/Arg instructions with Nop (parameters passed directly via local variables), simplifies Call references; isel_wasm does instruction mapping then skips register allocation (wasm is stack machine, no physical registers), emit_wasm outputs WAT text format. wasm32 pointer width is 32 bits (Km = Kw), no Kl type.
    • rv64: abi_rv64 lowers parameters to A0–A7 / FA0–FA7 per RISC-V calling convention, returns via A0/A1 / FA0/FA1; isel_rv64 maps IL instructions to RISC-V instructions (compare + branch merged directly, no flags, no magic number division, no complex addressing); then runs spill/rega same as amd64 — target differences switched at runtime via types.TargetCfg (init_amd64_target() / init_rv64_target()), emit_rv64 outputs RISC-V GAS assembly (fp/ra frame chain, 16-byte stack alignment).

    Three targets compared:

    amd64_sysvwasmrv64
    Library entrycompile / compile_debugcompile_wasm / compile_wasm_debugcompile_rv64 / compile_rv64_debug
    CLI-t amd64_sysv (default)-t wasm-t rv64
    Outputx86-64 GASWATRISC-V GAS
    Register allocationspill + regaskipped (stack machine)spill + rega (TargetCfg switch)
    Validation strengthDifferential regression byte-by-byteUnit tests + snapshotsUnit tests only (no reference baseline)

    #Debugging and Testing

    • Command-line -d <flags> provides per-stage dumps (-dP parse, -dM memopt, -dN SSA, -dC copy, -dF fold, -dA abi, -dI isel, -dL live, -dS spill, -dR rega), combinable; when debug is enabled, assembly is not output. Library entry compile_debug(text, flags) returns the same dump text.
    • Tests in three layers:
      • Unit/whitebox tests *_wbtest.mbt: Cover all compilation pipeline packages — types (BSet/Con/Ref/Op/Class/Jump etc.), util (Interner/formatting), lexer, parser, cfg (dominator tree/loop/jump simplification), ssa (phi insertion/copy/memopt), fold, live, abi/abi_wasm/abi_rv64, isel/isel_wasm/isel_rv64, spill, rega, emit/emit_wasm/emit_rv64, cmd/main;
      • Blackbox tests qbe_test.mbt + qbe_snapshot_test.mbt: Directly call @qbe.compile / @qbe.compile_debug, covering end-to-end compilation (arithmetic, floating-point, memory, recursion, loop phi) and error paths; qbe_snapshot_test.mbt generated by python tools/gen_snapshot_mbt.py from test/ categories, anchored with inspect snapshots;
      • Differential regression: test/*.ssa (406 cases) compared byte-by-byte with reference qbe binary (tools/qbe-ref pinned snapshot, built with make -C tools/qbe-ref) (python compare.py, can specify other binary with QBE_REF).
    • Run: moon test; update snapshots: moon test --update; coverage: moon coverage analyze.

    #Porting and Attribution Notes

    Original project information Original project name: Quick Backend (QBE)

    Original project link: https://github.com/8l/qbe

    This project license: Apache 2.0

    Original project license: MIT

    Original project license text
    © 2015-2017 Quentin Carbonneaux quentin@c9x.me Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    Compared to the original project, this project makes the following simplifications and redesigns:

    Rewrites code using MoonBit's modern ML-family language style instead of replicating C's suckless structure;

    Prioritizes implementing core backend capabilities that can run independently in MoonBit

    Rewrites manual memory management from C code to MoonBit's safe data structures and enum types, reducing memory risks;

    #Future Plans

    • ✅ WebAssembly (wasm32) code generation support (WAT text output)
    • ✅ RISC-V 64 (rv64) code generation (GAS output, reusing spill/rega)
    • rv64 backend improvements: data segment and floating-point constant rodata output, differential reference verification
    • Add convenient JIT-related interfaces
    • Interface with mbtcc to verify full end-to-end feasibility

    BSet

    using @azhzx/qbe/types { type BSet }

    Blk

    using @azhzx/qbe/types { type Blk }

    Class

    using @azhzx/qbe/types { type Class }

    Class - corresponds to enum Class in all.h

    Con

    using @azhzx/qbe/types { type Con }

    Dat

    using @azhzx/qbe/types { type Dat }

    using @azhzx/qbe/types { type Fn }

    Ins

    using @azhzx/qbe/types { type Ins }

    Jump

    using @azhzx/qbe/types { type Jump }

    using @azhzx/qbe/types { type Op }

    Ref

    using @azhzx/qbe/types { type Ref }

    ADT replacing C bit-field (uint type:3; uint val:29)

    Tmp

    using @azhzx/qbe/types { type Tmp }

    Typ

    using @azhzx/qbe/types { type Typ }

    compile

    fn compile(text : String, gas? : String) -> Result[String,
    QbeError
    ]

    compile_debug

    fn compile_debug(text : String, flags : String) -> Result[String,
    QbeError
    ]

    compile_rv64

    fn compile_rv64(text : String) -> Result[String,
    QbeError
    ]

    compile_rv64_debug

    fn compile_rv64_debug(text : String, flags : String) -> Result[String,
    QbeError
    ]

    compile_wasm

    fn compile_wasm(text : String) -> Result[String,
    QbeError
    ]

    compile_wasm_debug

    fn compile_wasm_debug(text : String, flags : String) -> Result[String,
    QbeError
    ]