///|
const CV_SIGNATURE_C13 : UInt32 = 4U

///|
const S_END : UInt16 = 0x0006

///|
const S_OBJNAME : UInt16 = 0x1101

///|
const S_THUNK32 : UInt16 = 0x1102

///|
const S_BLOCK32 : UInt16 = 0x1103

///|
const S_LABEL32 : UInt16 = 0x1105

///|
const S_CONSTANT : UInt16 = 0x1107

///|
const S_UDT : UInt16 = 0x1108

///|
const S_BPREL32 : UInt16 = 0x110B

///|
const S_LDATA32 : UInt16 = 0x110C

///|
const S_GDATA32 : UInt16 = 0x110D

///|
const S_PUB32 : UInt16 = 0x110E

///|
const S_LPROC32 : UInt16 = 0x110F

///|
const S_GPROC32 : UInt16 = 0x1110

///|
const S_REGREL32 : UInt16 = 0x1111

///|
const S_LTHREAD32 : UInt16 = 0x1112

///|
const S_GTHREAD32 : UInt16 = 0x1113

///|
const S_PROCREF : UInt16 = 0x1125

///|
const S_LPROCREF : UInt16 = 0x1127

///|
const S_FRAMEPROC : UInt16 = 0x1012

///|
const S_INLINESITE_END : UInt16 = 0x114E

///|
const S_PROC_ID_END : UInt16 = 0x114F

///|
/// Resource limits for CodeView symbol and module debug streams.
pub struct SymbolLimits {
  max_stream_bytes : Int32
  max_records : Int32
  max_record_bytes : Int32
  max_global_refs : Int32
  max_hash_records : Int32
  max_public_symbols : Int32
}

///|
pub fn SymbolLimits::default() -> SymbolLimits {
  SymbolLimits::{
    max_stream_bytes: 128 * 1024 * 1024,
    max_records: 2_000_000,
    max_record_bytes: 4 * 1024 * 1024,
    max_global_refs: 1_000_000,
    max_hash_records: 2_000_000,
    max_public_symbols: 1_000_000,
  }
}

///|
pub struct PublicSymbol {
  flags : UInt32
  offset : UInt32
  segment : UInt16
  name : String
}

///|
pub fn PublicSymbol::flags(self : PublicSymbol) -> UInt32 {
  self.flags
}

///|
pub fn PublicSymbol::offset(self : PublicSymbol) -> UInt32 {
  self.offset
}

///|
pub fn PublicSymbol::segment(self : PublicSymbol) -> UInt16 {
  self.segment
}

///|
pub fn PublicSymbol::name(self : PublicSymbol) -> String {
  self.name
}

///|
pub struct DataSymbol {
  type_index : UInt32
  offset : UInt32
  segment : UInt16
  name : String
}

///|
pub fn DataSymbol::type_index(self : DataSymbol) -> UInt32 {
  self.type_index
}

///|
pub fn DataSymbol::offset(self : DataSymbol) -> UInt32 {
  self.offset
}

///|
pub fn DataSymbol::segment(self : DataSymbol) -> UInt16 {
  self.segment
}

///|
pub fn DataSymbol::name(self : DataSymbol) -> String {
  self.name
}

///|
pub struct ProcedureSymbol {
  parent : UInt32
  end : UInt32
  next : UInt32
  code_size : UInt32
  debug_start : UInt32
  debug_end : UInt32
  function_type : UInt32
  code_offset : UInt32
  segment : UInt16
  flags : Int32
  name : String
}

///|
pub fn ProcedureSymbol::parent(self : ProcedureSymbol) -> UInt32 {
  self.parent
}

///|
pub fn ProcedureSymbol::end(self : ProcedureSymbol) -> UInt32 {
  self.end
}

///|
pub fn ProcedureSymbol::next(self : ProcedureSymbol) -> UInt32 {
  self.next
}

///|
pub fn ProcedureSymbol::code_size(self : ProcedureSymbol) -> UInt32 {
  self.code_size
}

///|
pub fn ProcedureSymbol::debug_start(self : ProcedureSymbol) -> UInt32 {
  self.debug_start
}

///|
pub fn ProcedureSymbol::debug_end(self : ProcedureSymbol) -> UInt32 {
  self.debug_end
}

///|
pub fn ProcedureSymbol::function_type(self : ProcedureSymbol) -> UInt32 {
  self.function_type
}

///|
pub fn ProcedureSymbol::code_offset(self : ProcedureSymbol) -> UInt32 {
  self.code_offset
}

///|
pub fn ProcedureSymbol::segment(self : ProcedureSymbol) -> UInt16 {
  self.segment
}

///|
pub fn ProcedureSymbol::flags(self : ProcedureSymbol) -> Int32 {
  self.flags
}

///|
pub fn ProcedureSymbol::name(self : ProcedureSymbol) -> String {
  self.name
}

///|
pub struct ThunkSymbol {
  parent : UInt32
  end : UInt32
  next : UInt32
  offset : UInt32
  segment : UInt16
  length : UInt16
  ordinal : Int32
  name : String
  variant_bytes : Bytes
}

///|
pub fn ThunkSymbol::parent(self : ThunkSymbol) -> UInt32 {
  self.parent
}

///|
pub fn ThunkSymbol::end(self : ThunkSymbol) -> UInt32 {
  self.end
}

///|
pub fn ThunkSymbol::next(self : ThunkSymbol) -> UInt32 {
  self.next
}

///|
pub fn ThunkSymbol::offset(self : ThunkSymbol) -> UInt32 {
  self.offset
}

///|
pub fn ThunkSymbol::segment(self : ThunkSymbol) -> UInt16 {
  self.segment
}

///|
pub fn ThunkSymbol::length(self : ThunkSymbol) -> UInt16 {
  self.length
}

///|
pub fn ThunkSymbol::ordinal(self : ThunkSymbol) -> Int32 {
  self.ordinal
}

///|
pub fn ThunkSymbol::name(self : ThunkSymbol) -> String {
  self.name
}

///|
pub fn ThunkSymbol::variant_bytes(self : ThunkSymbol) -> Bytes {
  self.variant_bytes
}

///|
pub struct BlockSymbol {
  parent : UInt32
  end : UInt32
  code_size : UInt32
  code_offset : UInt32
  segment : UInt16
  name : String
}

///|
pub fn BlockSymbol::parent(self : BlockSymbol) -> UInt32 {
  self.parent
}

///|
pub fn BlockSymbol::end(self : BlockSymbol) -> UInt32 {
  self.end
}

///|
pub fn BlockSymbol::code_size(self : BlockSymbol) -> UInt32 {
  self.code_size
}

///|
pub fn BlockSymbol::code_offset(self : BlockSymbol) -> UInt32 {
  self.code_offset
}

///|
pub fn BlockSymbol::segment(self : BlockSymbol) -> UInt16 {
  self.segment
}

///|
pub fn BlockSymbol::name(self : BlockSymbol) -> String {
  self.name
}

///|
pub struct LabelSymbol {
  code_offset : UInt32
  segment : UInt16
  flags : Int32
  name : String
}

///|
pub fn LabelSymbol::code_offset(self : LabelSymbol) -> UInt32 {
  self.code_offset
}

///|
pub fn LabelSymbol::segment(self : LabelSymbol) -> UInt16 {
  self.segment
}

///|
pub fn LabelSymbol::flags(self : LabelSymbol) -> Int32 {
  self.flags
}

///|
pub fn LabelSymbol::name(self : LabelSymbol) -> String {
  self.name
}

///|
pub struct ConstantSymbol {
  type_index : UInt32
  value : NumericValue
  name : String
}

///|
pub fn ConstantSymbol::type_index(self : ConstantSymbol) -> UInt32 {
  self.type_index
}

///|
pub fn ConstantSymbol::value(self : ConstantSymbol) -> NumericValue {
  self.value
}

///|
pub fn ConstantSymbol::name(self : ConstantSymbol) -> String {
  self.name
}

///|
pub struct UdtSymbol {
  type_index : UInt32
  name : String
}

///|
pub fn UdtSymbol::type_index(self : UdtSymbol) -> UInt32 {
  self.type_index
}

///|
pub fn UdtSymbol::name(self : UdtSymbol) -> String {
  self.name
}

///|
pub struct ObjectNameSymbol {
  signature : UInt32
  name : String
}

///|
pub fn ObjectNameSymbol::signature(self : ObjectNameSymbol) -> UInt32 {
  self.signature
}

///|
pub fn ObjectNameSymbol::name(self : ObjectNameSymbol) -> String {
  self.name
}

///|
pub struct BPRelativeSymbol {
  offset : Int32
  type_index : UInt32
  name : String
}

///|
pub fn BPRelativeSymbol::offset(self : BPRelativeSymbol) -> Int32 {
  self.offset
}

///|
pub fn BPRelativeSymbol::type_index(self : BPRelativeSymbol) -> UInt32 {
  self.type_index
}

///|
pub fn BPRelativeSymbol::name(self : BPRelativeSymbol) -> String {
  self.name
}

///|
pub struct RegRelativeSymbol {
  offset : UInt32
  type_index : UInt32
  register : UInt16
  name : String
}

///|
pub fn RegRelativeSymbol::offset(self : RegRelativeSymbol) -> UInt32 {
  self.offset
}

///|
pub fn RegRelativeSymbol::type_index(self : RegRelativeSymbol) -> UInt32 {
  self.type_index
}

///|
pub fn RegRelativeSymbol::register(self : RegRelativeSymbol) -> UInt16 {
  self.register
}

///|
pub fn RegRelativeSymbol::name(self : RegRelativeSymbol) -> String {
  self.name
}

///|
pub struct ProcedureReferenceSymbol {
  name_hash : UInt32
  symbol_offset : UInt32
  module_index : UInt16
  name : String
}

///|
pub fn ProcedureReferenceSymbol::name_hash(
  self : ProcedureReferenceSymbol,
) -> UInt32 {
  self.name_hash
}

///|
pub fn ProcedureReferenceSymbol::symbol_offset(
  self : ProcedureReferenceSymbol,
) -> UInt32 {
  self.symbol_offset
}

///|
pub fn ProcedureReferenceSymbol::module_index(
  self : ProcedureReferenceSymbol,
) -> UInt16 {
  self.module_index
}

///|
pub fn ProcedureReferenceSymbol::name(
  self : ProcedureReferenceSymbol,
) -> String {
  self.name
}

///|
pub struct FrameProcedureSymbol {
  total_frame_bytes : UInt32
  padding_frame_bytes : UInt32
  offset_to_padding : UInt32
  callee_saved_bytes : UInt32
  exception_handler_offset : UInt32
  exception_handler_section : UInt16
  flags : UInt32
}

///|
pub fn FrameProcedureSymbol::total_frame_bytes(
  self : FrameProcedureSymbol,
) -> UInt32 {
  self.total_frame_bytes
}

///|
pub fn FrameProcedureSymbol::padding_frame_bytes(
  self : FrameProcedureSymbol,
) -> UInt32 {
  self.padding_frame_bytes
}

///|
pub fn FrameProcedureSymbol::offset_to_padding(
  self : FrameProcedureSymbol,
) -> UInt32 {
  self.offset_to_padding
}

///|
pub fn FrameProcedureSymbol::callee_saved_bytes(
  self : FrameProcedureSymbol,
) -> UInt32 {
  self.callee_saved_bytes
}

///|
pub fn FrameProcedureSymbol::exception_handler_offset(
  self : FrameProcedureSymbol,
) -> UInt32 {
  self.exception_handler_offset
}

///|
pub fn FrameProcedureSymbol::exception_handler_section(
  self : FrameProcedureSymbol,
) -> UInt16 {
  self.exception_handler_section
}

///|
pub fn FrameProcedureSymbol::flags(self : FrameProcedureSymbol) -> UInt32 {
  self.flags
}

///|
pub struct UnknownSymbol {
  kind : UInt16
  payload : Bytes
}

///|
pub fn UnknownSymbol::kind(self : UnknownSymbol) -> UInt16 {
  self.kind
}

///|
pub fn UnknownSymbol::payload(self : UnknownSymbol) -> Bytes {
  self.payload
}

///|
/// The decoded payload of a known CodeView symbol record.
pub enum SymbolDetails {
  Public(PublicSymbol)
  Data(DataSymbol)
  Procedure(ProcedureSymbol)
  Thunk(ThunkSymbol)
  Block(BlockSymbol)
  Label(LabelSymbol)
  Constant(ConstantSymbol)
  Udt(UdtSymbol)
  ObjectName(ObjectNameSymbol)
  BPRelative(BPRelativeSymbol)
  RegRelative(RegRelativeSymbol)
  ProcedureReference(ProcedureReferenceSymbol)
  FrameProcedure(FrameProcedureSymbol)
  ScopeEnd
  Unknown(UnknownSymbol)
}

///|
pub struct SymbolRecord {
  offset : Int32
  kind : UInt16
  details : SymbolDetails
}

///|
pub fn SymbolRecord::offset(self : SymbolRecord) -> Int32 {
  self.offset
}

///|
pub fn SymbolRecord::kind(self : SymbolRecord) -> UInt16 {
  self.kind
}

///|
pub fn SymbolRecord::details(self : SymbolRecord) -> SymbolDetails {
  self.details
}

///|
/// Return the symbol name when its decoded kind carries one.
pub fn SymbolRecord::name(self : SymbolRecord) -> String? {
  match self.details {
    Public(value) => Some(value.name())
    Data(value) => Some(value.name())
    Procedure(value) => Some(value.name())
    Thunk(value) => Some(value.name())
    Block(value) => Some(value.name())
    Label(value) => Some(value.name())
    Constant(value) => Some(value.name())
    Udt(value) => Some(value.name())
    ObjectName(value) => Some(value.name())
    BPRelative(value) => Some(value.name())
    RegRelative(value) => Some(value.name())
    ProcedureReference(value) => Some(value.name())
    FrameProcedure(_) => None
    ScopeEnd => None
    Unknown(_) => None
  }
}

///|
pub struct SymbolTable {
  data : Bytes
  records : Array[SymbolRecord]
}

///|
pub fn SymbolTable::data(self : SymbolTable) -> Bytes {
  self.data
}

///|
pub fn SymbolTable::record_count(self : SymbolTable) -> Int32 {
  self.records.length()
}

///|
pub fn SymbolTable::record_at(
  self : SymbolTable,
  index : Int32,
) -> SymbolRecord? {
  if index < 0 || index >= self.records.length() {
    None
  } else {
    Some(self.records[index])
  }
}

///|
pub fn SymbolTable::records(self : SymbolTable) -> Array[SymbolRecord] {
  self.records.copy()
}

///|
pub fn SymbolTable::record_at_offset(
  self : SymbolTable,
  offset : Int32,
) -> SymbolRecord? {
  let mut low = 0
  let mut high = self.records.length()
  while low < high {
    let middle = low + (high - low) / 2
    let record = self.records[middle]
    if record.offset < offset {
      low = middle + 1
    } else {
      high = middle
    }
  }
  if low < self.records.length() && self.records[low].offset == offset {
    Some(self.records[low])
  } else {
    None
  }
}

///|
/// One module debug stream split into symbols, line data, and global refs.
pub struct ModuleDebugStream {
  signature : UInt32
  symbols : SymbolTable
  c11_lines : Bytes
  c13_lines : Bytes
  global_refs : Array[UInt32]
}

///|
pub fn ModuleDebugStream::signature(self : ModuleDebugStream) -> UInt32 {
  self.signature
}

///|
pub fn ModuleDebugStream::symbols(self : ModuleDebugStream) -> SymbolTable {
  self.symbols
}

///|
pub fn ModuleDebugStream::c11_lines(self : ModuleDebugStream) -> Bytes {
  self.c11_lines
}

///|
pub fn ModuleDebugStream::c13_lines(self : ModuleDebugStream) -> Bytes {
  self.c13_lines
}

///|
pub fn ModuleDebugStream::global_refs(
  self : ModuleDebugStream,
) -> Array[UInt32] {
  self.global_refs.copy()
}

///|
/// Parse a standalone CodeView symbol stream without a module signature.
pub fn parse_symbols(
  data : Bytes,
  limits : SymbolLimits,
) -> SymbolTable raise SymbolError {
  parse_symbols_from(data, 0, data.length(), limits)
}

///|
/// Parse the symbol-record stream referenced by the DBI header.
pub fn MsfFile::symbol_record_stream(
  self : MsfFile,
  dbi : DbiFile,
  limits : SymbolLimits,
) -> SymbolTable raise SymbolError {
  let index = dbi.header().symbol_record_stream_index()
  if index == 0xFFFF {
    raise SymbolStreamUnavailable(index)
  }
  let bytes = self.stream(index.to_int()) catch {
    _ => raise SymbolStreamUnavailable(index)
  }
  parse_symbols(bytes, limits)
}

///|
/// Read and parse one module's debug stream.
pub fn MsfFile::module_debug_stream(
  self : MsfFile,
  dbi : DbiFile,
  module_index : Int32,
  limits : SymbolLimits,
) -> ModuleDebugStream raise SymbolError {
  let item = match dbi.module_at(module_index) {
    Some(value) => value
    None => raise InvalidModuleStream("module index is outside the DBI table")
  }
  let index = item.module_stream_index()
  if index == 0xFFFF {
    raise ModuleStreamUnavailable(index)
  }
  let bytes = self.stream(index.to_int()) catch {
    _ => raise ModuleStreamUnavailable(index)
  }
  parse_module_debug_stream(
    bytes,
    item.symbol_debug_bytes(),
    item.c11_line_bytes(),
    item.c13_line_bytes(),
    limits,
  )
}

///|
/// Split and parse a module debug stream using sizes from its DBI descriptor.
pub fn parse_module_debug_stream(
  data : Bytes,
  symbol_bytes_raw : UInt32,
  c11_bytes_raw : UInt32,
  c13_bytes_raw : UInt32,
  limits : SymbolLimits,
) -> ModuleDebugStream raise SymbolError {
  if data.length() > limits.max_stream_bytes {
    raise SymbolLimitExceeded("module debug stream byte limit exceeded")
  }
  let symbol_bytes = symbol_uint_to_int(symbol_bytes_raw, "symbol bytes")
  let c11_bytes = symbol_uint_to_int(c11_bytes_raw, "C11 line bytes")
  let c13_bytes = symbol_uint_to_int(c13_bytes_raw, "C13 line bytes")
  if c11_bytes > 0 && c13_bytes > 0 {
    raise InvalidModuleStream("module has both C11 and C13 line data")
  }
  if symbol_bytes < 4 {
    raise InvalidModuleStream(
      "module symbol size is smaller than its signature",
    )
  }
  let symbol_end = symbol_bytes
  if symbol_end > data.length() {
    raise TruncatedSymbol(symbol_end)
  }
  let (signature, _) = read_symbol_u32(data, 0, 4)
  if signature != CV_SIGNATURE_C13 {
    raise UnsupportedModuleSignature(signature)
  }
  let symbols = parse_symbols_from(data, 4, symbol_end, limits)
  let c11_start = symbol_end
  let c11_end = checked_symbol_add(c11_start, c11_bytes, "C11 line data")
  let c13_end = checked_symbol_add(c11_end, c13_bytes, "C13 line data")
  if c13_end > data.length() {
    raise TruncatedSymbol(c13_end)
  }
  let c11_lines = data.exact_view(start=c11_start, end=c11_end).to_owned()
  let c13_lines = data.exact_view(start=c11_end, end=c13_end).to_owned()
  let global_refs = if c13_end == data.length() {
    []
  } else {
    let (count_raw, cursor) = read_symbol_u32(data, c13_end, data.length())
    let count = symbol_uint_to_int(count_raw, "global reference count")
    if count > limits.max_global_refs {
      raise SymbolLimitExceeded("global reference count limit exceeded")
    }
    let refs_end = checked_symbol_add(
      cursor,
      checked_symbol_mul(count, 4, "global references"),
      "global references",
    )
    if refs_end != data.length() {
      raise InvalidModuleStream(
        "global reference byte count does not match the module stream",
      )
    }
    let refs = []
    let mut position = cursor
    for _ in 0.. SymbolTable raise SymbolError {
  if data.length() > limits.max_stream_bytes {
    raise SymbolLimitExceeded("symbol stream byte limit exceeded")
  }
  if start < 0 || end < start || end > data.length() || start % 4 != 0 {
    raise InvalidSymbolRecord("invalid symbol-stream bounds or alignment")
  }
  let records = []
  let mut cursor = start
  while cursor < end {
    if records.length() >= limits.max_records {
      raise SymbolLimitExceeded("symbol record count limit exceeded")
    }
    let record_start = cursor
    let (length_raw, after_length) = read_symbol_u16(data, cursor, end)
    let length = length_raw.to_int()
    if length < 2 {
      raise InvalidSymbolRecord("symbol record is shorter than its kind field")
    }
    if length > limits.max_record_bytes {
      raise SymbolLimitExceeded("symbol record byte limit exceeded")
    }
    let record_end = checked_symbol_add(after_length, length, "symbol record")
    if record_end > end {
      raise TruncatedSymbol(record_end)
    }
    if (record_end - record_start) % 4 != 0 {
      raise InvalidSymbolRecord("symbol record is not 4-byte aligned")
    }
    let (kind, payload_start) = read_symbol_u16(data, after_length, record_end)
    let payload = data
      .exact_view(start=payload_start, end=record_end)
      .to_owned()
    records.push(SymbolRecord::{
      offset: record_start,
      kind,
      details: decode_symbol(kind, payload),
    })
    cursor = record_end
  }
  if cursor != end {
    raise InvalidSymbolRecord("symbol stream has trailing bytes")
  }
  SymbolTable::{ data, records, }
}

///|
fn decode_symbol(
  kind : UInt16,
  payload : Bytes,
) -> SymbolDetails raise SymbolError {
  match kind {
    S_PUB32 => decode_public_symbol(payload)
    S_LDATA32 | S_GDATA32 | S_LTHREAD32 | S_GTHREAD32 =>
      decode_data_symbol(payload)
    S_LPROC32 | S_GPROC32 => decode_procedure_symbol(payload)
    S_THUNK32 => decode_thunk_symbol(payload)
    S_BLOCK32 => decode_block_symbol(payload)
    S_LABEL32 => decode_label_symbol(payload)
    S_CONSTANT => decode_constant_symbol(payload)
    S_UDT => decode_udt_symbol(payload)
    S_OBJNAME => decode_object_name_symbol(payload)
    S_BPREL32 => decode_bp_relative_symbol(payload)
    S_REGREL32 => decode_reg_relative_symbol(payload)
    S_PROCREF | S_LPROCREF => decode_procedure_reference_symbol(payload)
    S_FRAMEPROC => decode_frame_procedure_symbol(payload)
    S_END | S_INLINESITE_END | S_PROC_ID_END => {
      require_symbol_padding(payload, 0, "scope-end record")
      ScopeEnd
    }
    _ => Unknown(UnknownSymbol::{ kind, payload, })
  }
}

///|
fn decode_public_symbol(payload : Bytes) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 11, "S_PUB32")
  let (flags, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (offset, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (segment, cursor) = read_symbol_u16(payload, cursor, payload.length())
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_PUB32")
  Public(PublicSymbol::{ flags, offset, segment, name, })
}

///|
fn decode_data_symbol(payload : Bytes) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 11, "S_DATA")
  let (type_index, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (offset, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (segment, cursor) = read_symbol_u16(payload, cursor, payload.length())
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_DATA")
  Data(DataSymbol::{ type_index, offset, segment, name, })
}

///|
fn decode_procedure_symbol(payload : Bytes) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 36, "S_PROC32")
  let (parent, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (end, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (next, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (code_size, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (debug_start, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (debug_end, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (function_type, cursor) = read_symbol_u32(
    payload,
    cursor,
    payload.length(),
  )
  let (code_offset, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (segment, cursor) = read_symbol_u16(payload, cursor, payload.length())
  let (flags, cursor) = read_symbol_u8(payload, cursor, payload.length())
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_PROC32")
  Procedure(ProcedureSymbol::{
    parent,
    end,
    next,
    code_size,
    debug_start,
    debug_end,
    function_type,
    code_offset,
    segment,
    flags,
    name,
  })
}

///|
fn decode_thunk_symbol(payload : Bytes) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 22, "S_THUNK32")
  let (parent, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (end, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (next, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (offset, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (segment, cursor) = read_symbol_u16(payload, cursor, payload.length())
  let (length, cursor) = read_symbol_u16(payload, cursor, payload.length())
  let (ordinal, cursor) = read_symbol_u8(payload, cursor, payload.length())
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  let variant_bytes = payload
    .exact_view(start=cursor, end=payload.length())
    .to_owned()
  Thunk(ThunkSymbol::{
    parent,
    end,
    next,
    offset,
    segment,
    length,
    ordinal,
    name,
    variant_bytes,
  })
}

///|
fn decode_block_symbol(payload : Bytes) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 19, "S_BLOCK32")
  let (parent, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (end, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (code_size, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (code_offset, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (segment, cursor) = read_symbol_u16(payload, cursor, payload.length())
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_BLOCK32")
  Block(BlockSymbol::{ parent, end, code_size, code_offset, segment, name, })
}

///|
fn decode_label_symbol(payload : Bytes) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 8, "S_LABEL32")
  let (code_offset, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (segment, cursor) = read_symbol_u16(payload, cursor, payload.length())
  let (flags, cursor) = read_symbol_u8(payload, cursor, payload.length())
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_LABEL32")
  Label(LabelSymbol::{ code_offset, segment, flags, name, })
}

///|
fn decode_constant_symbol(payload : Bytes) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 7, "S_CONSTANT")
  let (type_index, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (value, cursor) = read_numeric_value(payload, cursor, payload.length()) catch {
    _ => raise InvalidSymbolRecord("invalid constant numeric leaf")
  }
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_CONSTANT")
  Constant(ConstantSymbol::{ type_index, value, name, })
}

///|
fn decode_udt_symbol(payload : Bytes) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 5, "S_UDT")
  let (type_index, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_UDT")
  Udt(UdtSymbol::{ type_index, name, })
}

///|
fn decode_object_name_symbol(
  payload : Bytes,
) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 5, "S_OBJNAME")
  let (signature, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_OBJNAME")
  ObjectName(ObjectNameSymbol::{ signature, name, })
}

///|
fn decode_bp_relative_symbol(
  payload : Bytes,
) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 10, "S_BPREL32")
  let (offset, cursor) = read_symbol_i32(payload, 0, payload.length())
  let (type_index, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_BPREL32")
  BPRelative(BPRelativeSymbol::{ offset, type_index, name, })
}

///|
fn decode_reg_relative_symbol(
  payload : Bytes,
) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 12, "S_REGREL32")
  let (offset, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (type_index, cursor) = read_symbol_u32(payload, cursor, payload.length())
  let (register, cursor) = read_symbol_u16(payload, cursor, payload.length())
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_REGREL32")
  RegRelative(RegRelativeSymbol::{ offset, type_index, register, name, })
}

///|
fn decode_procedure_reference_symbol(
  payload : Bytes,
) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 12, "S_PROCREF")
  let (name_hash, cursor) = read_symbol_u32(payload, 0, payload.length())
  let (symbol_offset, cursor) = read_symbol_u32(
    payload,
    cursor,
    payload.length(),
  )
  let (module_index, cursor) = read_symbol_u16(
    payload,
    cursor,
    payload.length(),
  )
  let (name, cursor) = read_symbol_string(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_PROCREF")
  ProcedureReference(ProcedureReferenceSymbol::{
    name_hash,
    symbol_offset,
    module_index,
    name,
  })
}

///|
fn decode_frame_procedure_symbol(
  payload : Bytes,
) -> SymbolDetails raise SymbolError {
  require_symbol_bytes(payload, 26, "S_FRAMEPROC")
  let (total_frame_bytes, cursor) = read_symbol_u32(
    payload,
    0,
    payload.length(),
  )
  let (padding_frame_bytes, cursor) = read_symbol_u32(
    payload,
    cursor,
    payload.length(),
  )
  let (offset_to_padding, cursor) = read_symbol_u32(
    payload,
    cursor,
    payload.length(),
  )
  let (callee_saved_bytes, cursor) = read_symbol_u32(
    payload,
    cursor,
    payload.length(),
  )
  let (exception_handler_offset, cursor) = read_symbol_u32(
    payload,
    cursor,
    payload.length(),
  )
  let (exception_handler_section, cursor) = read_symbol_u16(
    payload,
    cursor,
    payload.length(),
  )
  let (flags, cursor) = read_symbol_u32(payload, cursor, payload.length())
  require_symbol_padding(payload, cursor, "S_FRAMEPROC")
  FrameProcedure(FrameProcedureSymbol::{
    total_frame_bytes,
    padding_frame_bytes,
    offset_to_padding,
    callee_saved_bytes,
    exception_handler_offset,
    exception_handler_section,
    flags,
  })
}

///|
fn read_symbol_string(
  data : Bytes,
  start : Int32,
  end : Int32,
) -> (String, Int32) raise SymbolError {
  if start < 0 || start >= end {
    raise TruncatedSymbol(start)
  }
  let mut cursor = start
  while cursor < end {
    if data.at(cursor) == 0 {
      let length = cursor - start
      if length > 1024 * 1024 {
        raise SymbolLimitExceeded("symbol name length limit exceeded")
      }
      let name = @utf8.decode_lossy(data.exact_view(start~, end=cursor))
      return (name, cursor + 1)
    }
    cursor = cursor + 1
  }
  raise InvalidSymbolRecord("symbol name is not NUL-terminated")
}

///|
fn require_symbol_padding(
  payload : Bytes,
  start : Int32,
  _field : String,
) -> Unit raise SymbolError {
  if start < 0 || start > payload.length() || payload.length() - start > 3 {
    raise InvalidSymbolRecord("symbol record has an invalid padding size")
  }
  for index in start.. Unit raise SymbolError {
  if payload.length() < minimum {
    raise TruncatedSymbol(payload.length())
  }
}

///|
fn read_symbol_u8(
  data : Bytes,
  pos : Int32,
  end : Int32,
) -> (Int32, Int32) raise SymbolError {
  if pos < 0 || pos >= end || pos >= data.length() {
    raise TruncatedSymbol(pos)
  }
  (data.at(pos).to_int(), pos + 1)
}

///|
fn read_symbol_u16(
  data : Bytes,
  pos : Int32,
  end : Int32,
) -> (UInt16, Int32) raise SymbolError {
  if pos < 0 || pos + 2 > end || pos + 2 > data.length() {
    raise TruncatedSymbol(pos)
  }
  (
    ((data.at(pos + 1).to_int() << 8) | data.at(pos).to_int()).to_uint16(),
    pos + 2,
  )
}

///|
fn read_symbol_u32(
  data : Bytes,
  pos : Int32,
  end : Int32,
) -> (UInt32, Int32) raise SymbolError {
  if pos < 0 || pos + 4 > end || pos + 4 > data.length() {
    raise TruncatedSymbol(pos)
  }
  (
    UInt32(
      (data.at(pos + 3).to_int().reinterpret_as_uint() << 24) |
      (data.at(pos + 2).to_int().reinterpret_as_uint() << 16) |
      (data.at(pos + 1).to_int().reinterpret_as_uint() << 8) |
      data.at(pos).to_int().reinterpret_as_uint(),
    ),
    pos + 4,
  )
}

///|
fn read_symbol_i32(
  data : Bytes,
  pos : Int32,
  end : Int32,
) -> (Int32, Int32) raise SymbolError {
  let (value, next) = read_symbol_u32(data, pos, end)
  (value.reinterpret_as_int(), next)
}

///|
fn symbol_uint_to_int(
  value : UInt32,
  field : String,
) -> Int32 raise SymbolError {
  if value > 0x7FFFFFFFU {
    raise InvalidSymbolRecord("\{field} exceeds Int range")
  }
  value.reinterpret_as_int()
}

///|
fn checked_symbol_add(
  left : Int32,
  right : Int32,
  field : String,
) -> Int32 raise SymbolError {
  if left < 0 || right < 0 || left > 0x7FFFFFFF - right {
    raise InvalidSymbolRecord("\{field} overflows Int range")
  }
  left + right
}

///|
fn checked_symbol_mul(
  left : Int32,
  right : Int32,
  field : String,
) -> Int32 raise SymbolError {
  if left < 0 || right < 0 || (left != 0 && right > 0x7FFFFFFF / left) {
    raise InvalidSymbolRecord("\{field} overflows Int range")
  }
  left * right
}