OJaml / docsProject documentation

Reference

Runtime Value Representation

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

The WebAssembly backend uses i32 as the universal value slot: every OJaml value that crosses a generated WebAssembly function boundary is carried in an i32 parameter or result. That does not mean every source value is an immediate integer. Integers and booleans are immediate i32 values, unit is zero, and floats are boxed f64 heap objects addressed by i32 pointers. Strings, tuples, records, algebraic data type values, arrays, lists, sets, maps, and closures are also heap pointers. WebAssembly function signatures stay uniform, while runtime interpretation depends on the static type chosen before emission.

The backend tradeoff is representation opacity. Uniform i32 values give direct calls, indirect calls, and polymorphic collection helpers the same WebAssembly signature shape. The cost is that WebAssembly itself no longer knows whether an i32 is an immediate integer, a boxed-float pointer, a string pointer, a tuple pointer, a record pointer, a list pointer, a set pointer, or a closure pointer. OJaml relies on the checker and specialization pass to preserve that meaning before emission.

float pointer f
  f + 0   f64 payload

array pointer a
  a + 0   length
  a + 4   element 0
  a + 8   element 1

tuple pointer t
  t + 0   arity
  t + 4   item 0
  t + 8   item 1

record pointer r
  r + 0   field count
  r + 4   sorted field 0
  r + 8   sorted field 1

variant pointer v
  v + 0   constructor tag
  v + 4   payload (when present)

list pointer l
  l + 0   head
  l + 4   tail pointer (0 = empty)

set pointer s
  s + 0   value
  s + 4   next pointer (0 = empty)

map pointer m
  m + 0   key
  m + 4   value
  m + 8   next pointer (0 = empty)

closure pointer c
  c + 0   table index
  c + 4   captured value 0
Heap object layouts. The runtime heap uses fixed layouts and linked structures, all addressed through i32 pointers.
alloc⁡(b)=p∧heap′=heap+b\operatorname{alloc}(b) = p\quad\land\quad heap' = heap + b
Allocation. The allocator is a monotonic bump pointer.

The heap begins after static string data. Allocation is bump-pointer allocation: alloc(bytes) returns the current heap pointer and advances it by the requested byte count. There is no garbage collector in the current implementation. Allocated tuples, records, variant values, arrays, cons cells, set entries, map entries, and closures live for the lifetime of the module instance.

τ∈{int,float,bool,unit,string,tuple,record,variant,array,list,set,map,fn}⇒wasm⁡(τ)=i32\tau \in \{int,float,bool,unit,string,tuple,record,variant,array,list,set,map,fn\}\Rightarrow \operatorname{wasm}(\tau)=i32
Uniform lowering. Static types differ in the checker, but emitted runtime values share the same WebAssembly value type.

Strings are emitted as WebAssembly data segments and represented by their memory offset. Floats are boxed by allocating eight bytes, storing an f64 payload there, and passing the resulting pointer through the same i32 value slot used by every other OJaml value. Float arithmetic and power unbox those pointers to f64 operands, perform the f64 operation, and box float results again. The runtime imports print_i32, print_f64, print_string, string primitives, pow_f64, and to_string support from JavaScript. The compiler chooses which import to call by consulting expression shape metadata derived from checked code.

  • Array.make traps negative lengths, and Array.get/Array.set trap null arrays, negative indexes, and indexes greater than or equal to the stored length.
  • Array.append allocates a new array and copies left values followed by right values; Array.reverse allocates a new array and copies elements from the end of the source to the front of the result; Array.exists and Array.for_all walk until the predicate determines the final bool.
  • Tuple values allocate fixed-size blocks and rely on the checker for arity and element-position consistency; tuple projection and fst/snd lower to fixed slot loads.
  • Record values allocate fixed-size blocks with fields sorted by label; field access and record patterns lower to fixed slot loads selected by the checked record type.
  • List.empty, Set.empty, and Map.empty are represented by null pointer 0; List.head and List.tail trap on empty lists, List.append copies the left spine while sharing the right list, List.reverse builds a new reversed spine, and List.exists/List.for_all stop as soon as the answer is known.
  • Set.add prepends a value only when Set.has cannot find an equal existing value; float sets compare unboxed f64 payloads.
  • Map.set prepends a key/value entry, making newer bindings shadow older equal keys.
  • Map.get traps if no matching key exists; Map.has remains the non-trapping presence check.