///|
// Flat little-endian byte memory for the SSA interpreter. Sparse: backed by
// a Map from address to byte, so the data segment, heap and stack can live
// in fixed conceptual regions without preallocating anything.

///|
priv struct Memory {
  bytes : Map[UInt64, Byte]
  // resolved symbol addresses (data labels)
  symbols : Map[String, UInt64]
}

///|
fn Memory::new() -> Memory {
  Memory::{ bytes: Map([], capacity=0), symbols: Map([], capacity=0), }
}

///|
fn Memory::store8(self : Memory, addr : UInt64, v : Int) -> Unit {
  self.bytes[addr] = (v & 0xFF).to_byte()
}

///|
fn Memory::store_int(self : Memory, addr : UInt64, v : Int64, n : Int) -> Unit {
  let u = v.reinterpret_as_uint64()
  for i in 0..> (i * 8)) & 0xFFUL).to_int()
    self.store8(addr + i.to_uint64(), b)
  }
}

///|
fn Memory::zero(self : Memory, addr : UInt64, n : Int64) -> Unit {
  // sparse memory is zero by default; nothing to do but the call documents
  // intent at the call sites
  ignore(addr)
  ignore(n)
}

///|
fn Memory::load8(self : Memory, addr : UInt64) -> Int {
  match self.bytes.get(addr) {
    Some(b) => b.to_int()
    None => 0
  }
}

///|
// Load an n-byte little-endian integer.
fn Memory::load_int(self : Memory, addr : UInt64, n : Int) -> Int64 {
  let mut v : UInt64 = 0UL
  for i in 0.. UInt64 {
  let mut v : UInt64 = 0UL
  for i in 0.. String {
  let sb = StringBuilder::StringBuilder()
  let mut p = addr
  for ;; {
    let c = self.load8(p)
    if c == 0 {
      break
    }
    sb.write_char(c.unsafe_to_char())
    p = p + 1UL
  }
  sb.to_string()
}

///|
// Store a string with NUL terminator, returning the end address.
fn Memory::store_cstr(self : Memory, addr : UInt64, s : String) -> UInt64 {
  let mut p = addr
  for c in s {
    let _ = self.bytes.set(p, c.to_int().to_byte())
    p = p + 1UL
  }
  let _ = self.bytes.set(p, b'\x00')
  p + 1UL
}