///|
// Instruction evaluation for the SSA interpreter. Integer semantics mirror
// the C QBE folder: word temporaries are kept sign-extended in 64 bits and
// word arithmetic truncates to 32 bits with sign extension.

///|
// Sign-extend the low 32 bits (C (int32_t) cast).
fn sext32(v : Int64) -> Int64 {
  v << 32 >> 32
}

///|
// Zero-extend the low 32 bits.
fn zx32(v : Int64) -> UInt64 {
  v.reinterpret_as_uint64() & 0xFFFFFFFFUL
}

///|
// Sign-extend the low 16 bits.
fn sext16(v : Int64) -> Int64 {
  v << 48 >> 48
}

///|
// Sign-extend the low 8 bits.
fn sext8(v : Int64) -> Int64 {
  v << 56 >> 56
}

///|
// The single-precision value stored in the low 32 bits.
fn bits_to_single(v : Int64) -> Float {
  let u : UInt = (v & 0xFFFFFFFFL).reinterpret_as_uint64().to_uint()
  Float::reinterpret_from_uint(u)
}

///|
fn bits_to_double(v : Int64) -> Double {
  v.reinterpret_as_double()
}

///|
// Evaluate a reference to a raw 64-bit word.
fn Interp::eval_ref(
  self : Interp,
  frame : Frame,
  r : @types.Ref,
) -> Int64 raise {
  match r {
    @types.RTmp(t) => frame.locals[t]
    @types.RCon(c) => {
      let con = frame.fn_.cons[c]
      match con.kind {
        @types.CBits => con.raw_bits()
        @types.CAddr => {
          let name = self.interner.get(con.label)
          match self.mem.symbols.get(name) {
            Some(addr) => addr.reinterpret_as_int64()
            None => self.code_of(name).reinterpret_as_int64()
          }
        }
        @types.CUndef => 0L
      }
    }
    _ =>
      raise @util.QbeError::CompileError(
        "interpreter: unsupported reference kind",
      )
  }
}

///|
// Evaluate an instruction to a raw 64-bit result word.
fn Interp::eval_ins(
  self : Interp,
  frame : Frame,
  ins : @types.Ins,
) -> Int64 raise {
  let k = ins.cls
  match ins.op {
    @types.Copy => self.eval_ref(frame, ins.arg1)
    // loads
    @types.Loadsb
    | @types.Loadub
    | @types.Loadsh
    | @types.Loaduh
    | @types.Loadsw
    | @types.Loaduw
    | @types.Load => {
      let addr = self.eval_ref(frame, ins.arg1).reinterpret_as_uint64()
      match ins.op {
        @types.Loadsb => sext8(self.mem.load_int(addr, 1))
        @types.Loadub => self.mem.load_int(addr, 1)
        @types.Loadsh => sext16(self.mem.load_int(addr, 2))
        @types.Loaduh => self.mem.load_int(addr, 2)
        @types.Loadsw => sext32(self.mem.load_int(addr, 4))
        @types.Loaduw => self.mem.load_int(addr, 4)
        // generic load: width from the class
        _ =>
          match k {
            @types.Kw => sext32(self.mem.load_int(addr, 4))
            @types.Ks => self.mem.load_int(addr, 4)
            @types.Kd => self.mem.load_int(addr, 8)
            _ => self.mem.load_int(addr, 8)
          }
      }
    }
    // stores
    @types.Storeb
    | @types.Storeh
    | @types.Storew
    | @types.Storel
    | @types.Stores
    | @types.Stored => {
      let addr = self.eval_ref(frame, ins.arg2).reinterpret_as_uint64()
      let v = self.eval_ref(frame, ins.arg1)
      let n = match ins.op {
        @types.Storeb => 1
        @types.Storeh => 2
        @types.Storew => 4
        @types.Stores => 4
        _ => 8
      }
      self.mem.store_int(addr, v, n)
      0L
    }
    // everything else is a pure operation on two operands (unary ops
    // leave arg2 as RNone)
    _ => {
      let a = self.eval_ref(frame, ins.arg1)
      let b = if ins.arg2.is_none() {
        0L
      } else {
        self.eval_ref(frame, ins.arg2)
      }
      self.eval_op(ins.op, k, a, b)
    }
  }
}

///|
// Evaluate a pure integer/float operation on raw words.
fn Interp::eval_op(
  self : Interp,
  op : @types.Op,
  k : @types.Class,
  a : Int64,
  b : Int64,
) -> Int64 raise {
  let is_word = k == @types.Kw
  // Conversions are dispatched by their result class, not the source
  // floating-point class.
  match op {
    @types.Stosi => {
      let v = bits_to_single(a).to_double().to_int64()
      return if is_word { sext32(v) } else { v }
    }
    @types.Dtosi => {
      let v = bits_to_double(a).to_int().to_int64()
      return if is_word { sext32(v) } else { v }
    }
    @types.Exts => return bits_to_single(a).to_double().reinterpret_as_int64()
    @types.Truncd =>
      return Float::from_double(bits_to_double(a))
        .reinterpret_as_uint()
        .to_int64()
    _ => ()
  }
  match op {
    @types.Ceqs
    | @types.Cnes
    | @types.Cles
    | @types.Clts
    | @types.Cges
    | @types.Cgts
    | @types.Cos
    | @types.Cuos => return self.eval_float_op(op, @types.Ks, a, b)
    @types.Ceqd
    | @types.Cned
    | @types.Cled
    | @types.Cltd
    | @types.Cged
    | @types.Cgtd
    | @types.Cod
    | @types.Cuod => return self.eval_float_op(op, @types.Kd, a, b)
    _ => ()
  }
  if k.base() == 1 {
    return self.eval_float_op(op, k, a, b)
  }
  let l = if is_word { sext32(a) } else { a }
  let r = if is_word { sext32(b) } else { b }
  match op {
    @types.Add => {
      let v = l + r
      if is_word {
        sext32(v)
      } else {
        v
      }
    }
    @types.Sub => {
      let v = l - r
      if is_word {
        sext32(v)
      } else {
        v
      }
    }
    @types.Mul => {
      let v = l * r
      if is_word {
        sext32(v)
      } else {
        v
      }
    }
    @types.Div => {
      if r == 0L {
        raise @util.QbeError::CompileError("interpreter: division by zero")
      }
      let v = if is_word { sext32(l / r) } else { l / r }
      v
    }
    @types.Rem => {
      if r == 0L {
        raise @util.QbeError::CompileError("interpreter: remainder by zero")
      }
      if is_word {
        sext32(l % r)
      } else {
        l % r
      }
    }
    @types.Udiv => {
      if (zx32(b) == 0UL && is_word) || b == 0L {
        raise @util.QbeError::CompileError("interpreter: division by zero")
      }
      if is_word {
        (zx32(a) / zx32(b)).reinterpret_as_int64()
      } else {
        (a.reinterpret_as_uint64() / b.reinterpret_as_uint64()).reinterpret_as_int64()
      }
    }
    @types.Urem => {
      if (zx32(b) == 0UL && is_word) || b == 0L {
        raise @util.QbeError::CompileError("interpreter: remainder by zero")
      }
      if is_word {
        (zx32(a) % zx32(b)).reinterpret_as_int64()
      } else {
        (a.reinterpret_as_uint64() % b.reinterpret_as_uint64()).reinterpret_as_int64()
      }
    }
    @types.And => l & r
    @types.Or => l | r
    @types.Xor => l ^ r
    @types.Sar => {
      let v = l >> ((r & 63L).to_int() & (if is_word { 31 } else { 63 }))
      v
    }
    @types.Shr => {
      let sh = (r & 63L).to_int() & (if is_word { 31 } else { 63 })
      let v = if is_word {
        (zx32(a) >> sh).reinterpret_as_int64()
      } else {
        (a.reinterpret_as_uint64() >> sh).reinterpret_as_int64()
      }
      if is_word {
        sext32(v)
      } else {
        v
      }
    }
    @types.Shl => {
      let sh = (r & 63L).to_int() & (if is_word { 31 } else { 63 })
      let v = a << sh
      if is_word {
        sext32(v)
      } else {
        v
      }
    }
    @types.Extsb => sext8(a)
    @types.Extub => a & 0xFF
    @types.Extsh => sext16(a)
    @types.Extuh => a & 0xFFFF
    @types.Extsw => sext32(a)
    @types.Extuw => a & 0xFFFFFFFF
    // comparisons: QBE compares produce words (0/1)
    @types.Ceqw | @types.Ceql => if l == r { 1L } else { 0L }
    @types.Cnew | @types.Cnel => if l != r { 1L } else { 0L }
    @types.Csgew | @types.Csgel => if l >= r { 1L } else { 0L }
    @types.Csgtw | @types.Csgtl => if l > r { 1L } else { 0L }
    @types.Cslew | @types.Cslel => if l <= r { 1L } else { 0L }
    @types.Csltw | @types.Csltl => if l < r { 1L } else { 0L }
    @types.Cugew | @types.Cugel =>
      if a.reinterpret_as_uint64() >= b.reinterpret_as_uint64() {
        1L
      } else {
        0L
      }
    @types.Cugtw | @types.Cugtl =>
      if a.reinterpret_as_uint64() > b.reinterpret_as_uint64() {
        1L
      } else {
        0L
      }
    @types.Culew | @types.Culel =>
      if a.reinterpret_as_uint64() <= b.reinterpret_as_uint64() {
        1L
      } else {
        0L
      }
    @types.Cultw | @types.Cultl =>
      if a.reinterpret_as_uint64() < b.reinterpret_as_uint64() {
        1L
      } else {
        0L
      }
    // float comparisons are dispatched on the class by eval_float_op;
    // reaching them here means an integer class mismatch
    @types.Ceqs
    | @types.Cnes
    | @types.Cles
    | @types.Clts
    | @types.Cges
    | @types.Cgts
    | @types.Cos
    | @types.Cuos
    | @types.Ceqd
    | @types.Cned
    | @types.Cled
    | @types.Cltd
    | @types.Cged
    | @types.Cgtd
    | @types.Cod
    | @types.Cuod => self.eval_float_op(op, k, a, b)
    // conversions
    @types.Cast => a
    _ =>
      raise @util.QbeError::CompileError(
        "interpreter: unsupported operation \{op.name()}",
      )
  }
}

///|
// Evaluate a floating point operation (single or double).
fn Interp::eval_float_op(
  self : Interp,
  op : @types.Op,
  k : @types.Class,
  a : Int64,
  b : Int64,
) -> Int64 raise {
  let single = k == @types.Ks
  // int -> float conversions
  match op {
    @types.Swtof => {
      let i = sext32(a)
      let v = if single {
        Float::from_int(i.to_int()).reinterpret_as_uint().to_int64()
      } else {
        i.to_int().to_double().reinterpret_as_int64()
      }
      return v
    }
    @types.Sltof => {
      let v = if single {
        Float::from_int(a.to_int()).reinterpret_as_uint().to_int64()
      } else {
        a.to_int().to_double().reinterpret_as_int64()
      }
      return v
    }
    _ => ()
  }
  if single {
    let x = bits_to_single(a)
    let y = bits_to_single(b)
    match op {
      @types.Add => (x + y).reinterpret_as_uint().to_int64()
      @types.Sub => (x - y).reinterpret_as_uint().to_int64()
      @types.Mul => (x * y).reinterpret_as_uint().to_int64()
      @types.Div => (x / y).reinterpret_as_uint().to_int64()
      @types.Ceqs => if x == y { 1L } else { 0L }
      @types.Cnes => if x != y { 1L } else { 0L }
      @types.Cles => if x <= y { 1L } else { 0L }
      @types.Clts => if x < y { 1L } else { 0L }
      @types.Cges => if x >= y { 1L } else { 0L }
      @types.Cgts => if x > y { 1L } else { 0L }
      @types.Cos => if x < y || x >= y { 1L } else { 0L }
      @types.Cuos => if !(x < y || x >= y) { 1L } else { 0L }
      _ =>
        raise @util.QbeError::CompileError(
          "interpreter: unsupported single operation \{op.name()}",
        )
    }
  } else {
    let x = bits_to_double(a)
    let y = bits_to_double(b)
    match op {
      @types.Add => (x + y).reinterpret_as_int64()
      @types.Sub => (x - y).reinterpret_as_int64()
      @types.Mul => (x * y).reinterpret_as_int64()
      @types.Div => (x / y).reinterpret_as_int64()
      @types.Ceqd => if x == y { 1L } else { 0L }
      @types.Cned => if x != y { 1L } else { 0L }
      @types.Cled => if x <= y { 1L } else { 0L }
      @types.Cltd => if x < y { 1L } else { 0L }
      @types.Cged => if x >= y { 1L } else { 0L }
      @types.Cgtd => if x > y { 1L } else { 0L }
      @types.Cod => if x < y || x >= y { 1L } else { 0L }
      @types.Cuod => if !(x < y || x >= y) { 1L } else { 0L }
      _ =>
        raise @util.QbeError::CompileError(
          "interpreter: unsupported double operation \{op.name()}",
        )
    }
  }
}