OJaml / docsProject documentation

Reference

WebAssembly Backend

Look up syntax, contracts, layouts, algorithms, and exact behavior.

The compiler emits a complete WebAssembly text module. The module declares the arity-specific function types needed for indirect calls in that program, imports print_i32, print_f64, print_string, string host helpers, pow_f64, and to_string support, exports memory and main, defines a function table, owns a mutable heap global, emits standard-library helpers, emits closure wrappers, emits top-level declarations and int/float specializations, emits pending lambdas, and appends string data segments. User module values are emitted as qualified globals such as Scores.total or Scores.Offsets.make; module-local types and value signatures feed the checker, module-local variants feed the constructor table, opened constructors are resolved to their qualified constructor tags before emission, and modules are a compile-time namespace layer rather than runtime records.

(module
  (type $fn_1 ...)
  ...
  (type $fn_n ...)
  (import "env" "print_i32" ...)
  (import "env" "print_f64" ...)
  (import "env" "print_string" ...)
  (import "env" "string_concat" ...)
  (import "env" "pow_f64" ...)
  (import "env" "to_string" ...)
  (memory (export "memory") 1)
  (table N funcref)
  (global $heap (mut i32) ...)

  ;; allocator and collection helpers
  ;; top-level closure wrappers
  ;; user declarations and int/float specializations
  ;; pending lambdas
  ;; string data segments

  (export "main" (func $main)))
Generated module skeleton. The backend emits a self-contained module with runtime helpers and the user's program.
let main = 40 + 2

;; lowers approximately to:
(func $main (result i32)
  (i32.add (i32.const 40) (i32.const 2)))
Expression lowering sketch. Primitive scalar expressions lower directly to simple WebAssembly instructions.
emit⁡(f  a1…an)=(call  $f  emit⁡(a1)…emit⁡(an))\operatorname{emit}(f\;a_1\ldots a_n)=(call\;\$f\;\operatorname{emit}(a_1)\ldots\operatorname{emit}(a_n))
Direct call. Known global callees are emitted as direct WebAssembly calls.

Direct calls are emitted when the callee is a known global function. Polymorphic top-level functions may emit concrete int and float variants so a function such as square can be used at both square 9 and square 2.5 without confusing immediate ints with boxed-float pointers. Local function values, captured function values, anonymous functions, returned closures, staged closures, and top-level functions used as values are emitted through closure allocation and call_indirect. The backend generates call_indirect types up to the maximum arity used by those function values, keeping a direct call path for known functions without imposing a practical source-level argument limit on closures.

emit⁡(g  a1…an)=call_indirect⁡($fnn,  env=g,  args,  table=load(g))\operatorname{emit}(g\;a_1\ldots a_n)=\operatorname{call\_indirect}(\$fn_n,\;env=g,\;args,\;table=load(g))
Indirect call. Function values are closure pointers and call through the function table.

Expression emission follows the AST. Literals become constants, boxed floats, or string offsets. Tuple expressions allocate a fixed-size block, store the arity, then store each element in order. Tuple projection compiles to a fixed offset load after checking the receiver shape and index. Record expressions allocate the same block shape with fields sorted by label, so access and pattern matching use stable offsets even when source field order varies. Binary operators become i32 or f64 operations depending on checked expression shape. Power is right-associative; int ** int lowers through an integer result helper, while any float operand routes through pow_f64 and returns a boxed float. Pipeline emission reuses normal call emission with the left expression appended as the single argument, so direct calls, opened stdlib calls, closures, returned closures, and int/float specializations stay on the same path. Local lets become blocks that set locals then evaluate the body; local recursive functions allocate a closure that can capture its own pointer. Conditionals and matches become structured WebAssembly if expressions. Function values become closure pointers.

  • safe(name) maps source names like Map.get to valid WebAssembly identifiers such as Map_get.
  • StringPool interns string literals and emits one null-terminated data segment per distinct value.
  • The table contains top-level closure wrappers followed by anonymous lambda functions.
  • Indirect-call function types are generated up to the highest closure arity used by the program.
  • The backend emits all user-level values as i32, relying on prior static checks and int/float specialization for meaning.