aver-lang 0.17.2

VM and transpiler for Aver, a statically-typed language designed for AI-assisted development
Documentation
;; Growable byte buffer — backing storage for the deforestation
;; lowering planned for 0.15 "Traversal". Builders that match the
;; canonical `List.prepend → reverse → String.join` shape compile to
;; a sequence of `rt_buffer_append_str` calls that write straight into
;; this buffer, skipping cons cells and the intermediate List entirely.
;;
;; Layout:
;;   offset 0  : i64 header — (kind=13, OBJ_BUFFER) << 56 | byte_len
;;   offset 8  : i64 cap — total payload bytes allocated (excludes
;;               header).
;;   offset 16+: raw payload bytes.
;;
;; `rt_buffer_finalize` converts an OBJ_BUFFER in-place to an
;; OBJ_STRING by `memory.copy`-moving the payload from offset 16 down
;; to offset 8 (the OBJ_STRING payload offset) and rewriting the
;; header with `kind=0`. The finalized object is shorter than what
;; was reserved (we leave the old cap field as dead bytes), but
;; subsequent allocations bump past it via the heap_ptr global so it
;; doesn't matter for correctness — only for fragmentation, which
;; the next GC collect cycle reclaims.
;;
;; The collector treats OBJ_BUFFER as a leaf object: payload is raw
;; bytes, no inner pointers to retain. Size for traversal is
;; 16 + align8(cap).

;; rt_buffer_new(cap_hint) -> ptr
;; Allocate a fresh OBJ_BUFFER with capacity `cap_hint` bytes and
;; len=0. Caller is responsible for picking a reasonable cap_hint
;; (the lowering pass uses a heuristic; runtime grows on demand).
(func $rt_buffer_new (param $cap_hint i32) (result i32)
  (local $ptr i32)
  (local $alloc_size i32)

  ;; alloc_size = 16 (header + cap) + align8(cap_hint)
  local.get $cap_hint
  i32.const 7
  i32.add
  i32.const -8
  i32.and
  i32.const 16
  i32.add
  local.set $alloc_size

  local.get $alloc_size
  call $rt_alloc
  local.set $ptr

  ;; header at offset 0 = (kind=13 << 56) | len=0
  local.get $ptr
  i64.const 0x0D00000000000000
  i64.store

  ;; cap at offset 8
  local.get $ptr
  local.get $cap_hint
  i64.extend_i32_u
  i64.store offset=8

  local.get $ptr
)
(export "rt_buffer_new" (func $rt_buffer_new))

;; rt_buffer_grow(buf, min_cap) -> new_ptr
;; Internal: allocate a new buffer with capacity max(min_cap, 2*old_cap),
;; memory.copy the old payload over, return the new pointer. The old
;; buffer becomes garbage for the next GC pass.
(func $rt_buffer_grow (param $buf i32) (param $min_cap i32) (result i32)
  (local $old_cap i32)
  (local $old_len i32)
  (local $new_cap i32)
  (local $doubled i32)
  (local $new_buf i32)

  ;; old_cap = i32 truncation of i64 at offset 8
  local.get $buf
  i64.load offset=8
  i32.wrap_i64
  local.set $old_cap

  ;; old_len = low 32 bits of header
  local.get $buf
  i64.load
  i64.const 0xFFFFFFFF
  i64.and
  i32.wrap_i64
  local.set $old_len

  ;; doubled = old_cap * 2
  local.get $old_cap
  i32.const 1
  i32.shl
  local.set $doubled

  ;; new_cap = max(min_cap, doubled). Branch-free: pick whichever is
  ;; larger via select.
  local.get $min_cap
  local.get $doubled
  local.get $min_cap
  local.get $doubled
  i32.gt_u
  select
  local.set $new_cap

  ;; Allocate new buffer.
  local.get $new_cap
  call $rt_buffer_new
  local.set $new_buf

  ;; memory.copy(new_buf+16, old_buf+16, old_len). Skip if old_len=0.
  local.get $old_len
  i32.eqz
  if
  else
    local.get $new_buf
    i32.const 16
    i32.add
    local.get $buf
    i32.const 16
    i32.add
    local.get $old_len
    memory.copy
  end

  ;; new_buf header = (kind=13 << 56) | old_len. Carry the existing
  ;; len through; caller's append will bump it after copying its own
  ;; bytes in.
  local.get $new_buf
  i64.const 0x0D00000000000000
  local.get $old_len
  i64.extend_i32_u
  i64.or
  i64.store

  local.get $new_buf
)

;; rt_buffer_append_str(buf, str) -> ptr
;; Copy the payload bytes of OBJ_STRING `str` into `buf` at offset
;; 16 + buf.len, then bump buf.len by str.byte_len. Returns the
;; possibly-relocated buffer pointer; caller MUST use the returned
;; value (not the input `buf`) for any subsequent operations.
(func $rt_buffer_append_str (param $buf i32) (param $str i32) (result i32)
  (local $cur_buf i32)
  (local $buf_len i32)
  (local $buf_cap i32)
  (local $str_len i32)
  (local $needed i32)

  local.get $buf
  local.set $cur_buf

  ;; str_len = low 32 bits of str's header.
  local.get $str
  i64.load
  i64.const 0xFFFFFFFF
  i64.and
  i32.wrap_i64
  local.set $str_len

  ;; Fast path: zero-length string is a no-op.
  local.get $str_len
  i32.eqz
  if
    local.get $cur_buf
    return
  end

  ;; buf_len = low 32 bits of buf's header.
  local.get $cur_buf
  i64.load
  i64.const 0xFFFFFFFF
  i64.and
  i32.wrap_i64
  local.set $buf_len

  ;; buf_cap = i32 of cap field at offset 8.
  local.get $cur_buf
  i64.load offset=8
  i32.wrap_i64
  local.set $buf_cap

  ;; needed = buf_len + str_len.
  local.get $buf_len
  local.get $str_len
  i32.add
  local.set $needed

  ;; If needed > buf_cap, grow.
  local.get $needed
  local.get $buf_cap
  i32.gt_u
  if
    local.get $cur_buf
    local.get $needed
    call $rt_buffer_grow
    local.set $cur_buf
    ;; After grow, refresh buf_len (carried over by grow).
    local.get $cur_buf
    i64.load
    i64.const 0xFFFFFFFF
    i64.and
    i32.wrap_i64
    local.set $buf_len
  end

  ;; memory.copy(cur_buf + 16 + buf_len, str + 8, str_len).
  local.get $cur_buf
  i32.const 16
  i32.add
  local.get $buf_len
  i32.add
  local.get $str
  i32.const 8
  i32.add
  local.get $str_len
  memory.copy

  ;; Update header: len = buf_len + str_len, kind stays 11.
  local.get $cur_buf
  i64.const 0x0D00000000000000
  local.get $needed
  i64.extend_i32_u
  i64.or
  i64.store

  local.get $cur_buf
)
(export "rt_buffer_append_str" (func $rt_buffer_append_str))

;; rt_buffer_finalize(buf) -> str
;; In-place conversion: shift payload from offset 16 down to offset
;; 8 (the OBJ_STRING payload position), rewrite header to OBJ_STRING
;; (kind=0). Returns the same pointer, now usable as an OBJ_STRING.
;; The dead cap field at offset 8 (now overwritten by payload) is
;; discarded; the +8 bytes of slack at the end stay until GC.
(func $rt_buffer_finalize (param $buf i32) (result i32)
  (local $len i32)

  ;; len = low 32 bits of header.
  local.get $buf
  i64.load
  i64.const 0xFFFFFFFF
  i64.and
  i32.wrap_i64
  local.set $len

  ;; memory.copy(buf+8, buf+16, len). memory.copy handles overlapping
  ;; regions (semantics: as if temporary buffer used). Skip if len=0.
  local.get $len
  i32.eqz
  if
  else
    local.get $buf
    i32.const 8
    i32.add
    local.get $buf
    i32.const 16
    i32.add
    local.get $len
    memory.copy
  end

  ;; Header = (kind=0 << 56) | len. Just keep low 32 bits, clear
  ;; kind nibble.
  local.get $buf
  local.get $len
  i64.extend_i32_u
  i64.store

  local.get $buf
)
(export "rt_buffer_finalize" (func $rt_buffer_finalize))