← Back to Architecture & Comparison Guides
Published: 2026-10-26 Updated: 2026-10-26 Source: bend2.org

Bend vs. Mojo: MLIR GPU Kernel Engineering vs. Proof-Checked Parallel Tree Reductions

Technical comparison of Modular’s Mojo (MLIR, SIMD tensors, explicit GPU kernels, Python interop) and Bend (BendTT 0.38 s formal proofs, affine Task Cube, and automatic CPU/GPU tree parallelism).


Executive Summary: Bottom Line Up Front

Mojo and Bend both adopt Python-inspired indentation syntax and ownership-based memory management to eliminate Python’s interpreter overhead and unlock GPU hardware, but they target opposite levels of abstraction:

  1. Choose Mojo (mojo build, MAX Engine, MLIR) when you are writing dense tensor kernels, SIMD-vectorized array pipelines, custom neural-network operators, or low-level GPU kernels where you want explicit control over thread indices (thread_idx.x), shared SRAM tiles, warp-level matrix instructions, and direct CPython ecosystem interop.
  2. Choose Bend (bend -o, LAWS.bend, PROOF.bend) when you are building recursive tree/graph algorithms, symbolic evaluators, compilers, or state machines that require machine-checked mathematical proofs (0.38 s verification loop) and lock-free CPU/GPU parallelism without writing device kernels. Mojo has no dependent types or theorem prover, and its GPU model requires separate kernel functions over flat device buffers. Bend compiles the exact same recursive .bend function to multi-core CPU (--threads N) and Apple Metal / NVIDIA CUDA (--gpu on), achieving up to 123.9x GPU speedup on Apple M4 Max.

1. Architectural Comparison: Mojo (MLIR) vs. Bend (BendTT + BendRT)

Architectural Dimension Mojo (Modular / MLIR) Bend (HigherOrderCO / BendTT + BendRT)
Compiler Backend MLIR + LLVM ahead-of-time and JIT compiler targeting CPU SIMD and NVIDIA/AMD GPUs. BendTT checker -> proof erasure -> C11 / Metal / CUDA linked against BendRT (2.0 to 2.8 MB RSS).
Memory & Ownership Model Rust-style ownership (owned, borrowed, inout) with deterministic ___del__ destruction and mutable references. Affine functional ownership (variables used at most once; closures never copied; explicit & counted sharing for immutable Data).
CPU Parallelism Explicit parallelize[func](num_work_items) tasks on a runtime work-stealing thread pool + explicit SIMD[DType, width] vectors. Balanced parallel forks (a! b! = f(x) & g(y)) mapped onto BendRT’s static 128x128 Task Cube (16,384 lock-free rings).
GPU Execution Model Explicit SIMT kernels: programmer writes kernel functions using DeviceContext, block_idx, thread_idx, and explicit device buffers. Unified functional code: the same recursive Bend function runs on CPU or GPU via ! without thread indices or barriers.
Correctness & Formal Proofs Static types, trait bounds, compile-time @parameter metaprogramming, and lifetime checks; no theorem prover. Dependent type theory (BendTT): law propositions proved by def functions and checked in 0.38 s (bend --verdict backed by 20,981 lines of Lean 4).
Termination Guarantee Unrestricted while and for loops (Turing-complete; non-termination permitted). Structural recursion enforced on every function (case 1n + p: must shrink argument) so proofs cannot loop forever.
Primary Data Structures Contiguous flat tensors, LayoutTensor, SIMD registers, and mutable structs. Immutable algebraic data types (type ... is Data:), recursive binary trees, ASTs, and quadtrees.

2. GPU Programming Model: Explicit Thread Blocks (Mojo) vs. Balanced Forks (Bend)

2.1 How Mojo Programs the GPU

Mojo gives systems engineers a unified syntax for host and device code on top of MLIR, replacing CUDA C++ syntax while preserving the hardware SIMT execution model. To run code on a GPU in Mojo, you allocate host and device buffers, compute grid and block dimensions, and index into flat arrays using block_idx and thread_idx. For dense matrix multiplication (MatMul), FlashAttention, and custom quantization kernels, Mojo’s explicit control over tensor cores and shared memory is ideal.

2.2 How Bend Programs the GPU

In Bend, there are no thread indices, block dimensions, barrier synchronizations, or mutable device buffers. You write a pure recursive function over an algebraic data type and mark independent recursive calls with !:

type Tree(T) is Data:
  Leaf {val: T}
  Node {left: Tree(T), right: Tree(T)}

def sum_tree(tree: Tree(U32)) -> U32:
  match tree:
    case Leaf{val}:
      val
    case Node{left, right}:
      a! b! = sum_tree(left) & sum_tree(right)
      a + b

Because Bend enforces affine ownership (and & counted sharing is strictly immutable), sum_tree(left) and sum_tree(right) can never race on shared memory. BendRT maps the binary recursion tree across its 128x128 Task Cube (16,384 tasks) during the bulk-synchronous Grow and Work phases.


3. Compile-Time Metaprogramming (@parameter) vs. Dependent Proofs (LAWS.bend)

Mojo features powerful compile-time metaprogramming (alias, @parameter for, compile-time tensor shape specialization), which is often confused with dependent verification: - Mojo @parameter Specialization: Evaluates known constants, loop unrolling factors, and SIMD widths at compile time, catching shape or trait mismatches for the specific concrete dimensions instantiated by the build. - Bend Universal Inductive Proofs (law / PROOF.bend): Proves that a property holds for all possible runtime inputs (all order: Order, all tree: Tree(U32)) via structural induction and normalization. When an AI coding agent modifies a Bend function, bend PROOF.bend verifies in 0.38 seconds that no possible input can violate the specification in LAWS.bend.