///|
pub suberror ArtifactEncodeError {
  InvalidArtifactField(field~ : String, message~ : String)
} derive(Eq, Debug)

///|
pub suberror ArtifactDecodeError {
  ArtifactTooLarge(size~ : Int, limit~ : Int)
  BadMagic
  UnsupportedVersion(version~ : Int)
  Truncated(field~ : String, offset~ : Int, needed~ : Int, remaining~ : Int)
  InvalidTag(field~ : String, tag~ : Int)
  InvalidValue(field~ : String, message~ : String)
  InvalidUtf8(field~ : String)
  LimitExceeded(field~ : String, count~ : Int, limit~ : Int)
  TrailingBytes(count~ : Int)
} derive(Eq, Debug)

///|
/// Resource limits applied before allocating variable-length artifact data.
pub(all) struct DecodeLimits {
  max_artifact_bytes : Int
  max_imports : Int
  max_functions : Int
  max_cpu_features : Int
  max_string_bytes : Int
  max_signature_values : Int
  max_code_bytes_per_function : Int
  max_total_code_bytes : Int
  max_relocations_per_function : Int
  max_sources_per_function : Int
  max_traps_per_function : Int
  max_safepoints_per_function : Int
  max_unwind_directives_per_function : Int
  max_roots_per_safepoint : Int
} derive(Eq, Debug)

///|
pub fn DecodeLimits::default() -> DecodeLimits {
  {
    max_artifact_bytes: 512 * 1024 * 1024,
    max_imports: 100_000,
    max_functions: 1_000_000,
    max_cpu_features: 256,
    max_string_bytes: 1024 * 1024,
    max_signature_values: 10_000,
    max_code_bytes_per_function: 128 * 1024 * 1024,
    max_total_code_bytes: 512 * 1024 * 1024,
    max_relocations_per_function: 1_000_000,
    max_sources_per_function: 1_000_000,
    max_traps_per_function: 1_000_000,
    max_safepoints_per_function: 1_000_000,
    max_unwind_directives_per_function: 1_000_000,
    max_roots_per_safepoint: 1_000_000,
  }
}

///|
priv struct ByteWriter {
  bytes : Array[Byte]
}

///|
fn ByteWriter::new() -> ByteWriter {
  { bytes: [] }
}

///|
fn ByteWriter::write_u8(self : ByteWriter, value : Int) -> Unit {
  self.bytes.push((value & 0xFF).to_byte())
}

///|
fn ByteWriter::write_u32(
  self : ByteWriter,
  value : Int,
  field : String,
) -> Unit raise ArtifactEncodeError {
  if value < 0 || value.to_int64() > 0xFFFF_FFFFL {
    raise InvalidArtifactField(
      field~,
      message="expected an unsigned 32-bit value",
    )
  }
  self.write_u8(value)
  self.write_u8(value >> 8)
  self.write_u8(value >> 16)
  self.write_u8(value >> 24)
}

///|
fn ByteWriter::write_i64(self : ByteWriter, value : Int64) -> Unit {
  let bits = value.reinterpret_as_uint64()
  for shift in 0..<8 {
    self.write_u8((bits >> (shift * 8)).to_int())
  }
}

///|
fn ByteWriter::write_bool(self : ByteWriter, value : Bool) -> Unit {
  self.write_u8(if value { 1 } else { 0 })
}

///|
fn ByteWriter::write_bytes(self : ByteWriter, value : Bytes) -> Unit {
  for byte in value {
    self.bytes.push(byte)
  }
}

///|
fn ByteWriter::write_byte_array(self : ByteWriter, value : Array[Byte]) -> Unit {
  for byte in value {
    self.bytes.push(byte)
  }
}

///|
fn ByteWriter::write_string(
  self : ByteWriter,
  value : String,
  field : String,
) -> Unit raise ArtifactEncodeError {
  let encoded = @utf8.encode(value)
  self.write_u32(encoded.length(), field)
  self.write_bytes(encoded)
}

///|
fn ByteWriter::finish(self : ByteWriter) -> Bytes {
  Bytes::from_array(self.bytes)
}

///|
priv struct ByteReader {
  data : Bytes
  mut position : Int
  limits : DecodeLimits
}

///|
fn ByteReader::new(data : Bytes, limits : DecodeLimits) -> ByteReader {
  { data, position: 0, limits }
}

///|
fn ByteReader::remaining(self : ByteReader) -> Int {
  self.data.length() - self.position
}

///|
fn ByteReader::read_u8(
  self : ByteReader,
  field : String,
) -> Int raise ArtifactDecodeError {
  if self.remaining() < 1 {
    raise Truncated(field~, offset=self.position, needed=1, remaining=0)
  }
  let value = self.data[self.position].to_int()
  self.position += 1
  value
}

///|
fn ByteReader::read_u32(
  self : ByteReader,
  field : String,
) -> Int raise ArtifactDecodeError {
  if self.remaining() < 4 {
    raise Truncated(
      field~,
      offset=self.position,
      needed=4,
      remaining=self.remaining(),
    )
  }
  let b0 = self.data[self.position].to_uint()
  let b1 = self.data[self.position + 1].to_uint()
  let b2 = self.data[self.position + 2].to_uint()
  let b3 = self.data[self.position + 3].to_uint()
  self.position += 4
  let value = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
  if value > 0x7FFF_FFFFU {
    raise InvalidValue(
      field~,
      message="unsigned 32-bit value exceeds the host Int range",
    )
  }
  value.reinterpret_as_int()
}

///|
fn ByteReader::read_i64(
  self : ByteReader,
  field : String,
) -> Int64 raise ArtifactDecodeError {
  if self.remaining() < 8 {
    raise Truncated(
      field~,
      offset=self.position,
      needed=8,
      remaining=self.remaining(),
    )
  }
  let mut bits = 0UL
  for shift in 0..<8 {
    bits = bits |
      (self.data[self.position + shift].to_int().to_uint64() << (shift * 8))
  }
  self.position += 8
  bits.reinterpret_as_int64()
}

///|
fn ByteReader::read_int_from_i64(
  self : ByteReader,
  field : String,
) -> Int raise ArtifactDecodeError {
  let value = self.read_i64(field)
  if value < -0x80000000L || value > 0x7FFFFFFFL {
    raise InvalidValue(
      field~,
      message="signed 64-bit value exceeds the host Int range",
    )
  }
  value.to_int()
}

///|
fn ByteReader::read_bool(
  self : ByteReader,
  field : String,
) -> Bool raise ArtifactDecodeError {
  match self.read_u8(field) {
    0 => false
    1 => true
    tag => raise InvalidTag(field~, tag~)
  }
}

///|
fn ByteReader::read_raw(
  self : ByteReader,
  count : Int,
  field : String,
) -> Bytes raise ArtifactDecodeError {
  if count < 0 {
    raise InvalidValue(field~, message="negative byte count")
  }
  let remaining = self.remaining()
  if count > remaining {
    raise Truncated(field~, offset=self.position, needed=count, remaining~)
  }
  let result : Array[Byte] = []
  for i in 0.. Int raise ArtifactDecodeError {
  let count = self.read_u32(field)
  if count > limit {
    raise LimitExceeded(field~, count~, limit~)
  }
  count
}

///|
fn ByteReader::read_string(
  self : ByteReader,
  field : String,
) -> String raise ArtifactDecodeError {
  let count = self.read_count(field + ".length", self.limits.max_string_bytes)
  let raw = self.read_raw(count, field)
  @utf8.decode(raw[:]) catch {
    _ => raise InvalidUtf8(field~)
  }
}

///|
fn bytes_to_array(bytes : Bytes) -> Array[Byte] {
  let result : Array[Byte] = []
  for byte in bytes {
    result.push(byte)
  }
  result
}

///|
fn write_architecture(
  writer : ByteWriter,
  value : @code_object.Architecture,
) -> Unit {
  writer.write_u8(
    match value {
      AArch64 => 0
      X64 => 1
    },
  )
}

///|
fn read_architecture(
  reader : ByteReader,
) -> @code_object.Architecture raise ArtifactDecodeError {
  match reader.read_u8("manifest.target.architecture") {
    0 => AArch64
    1 => X64
    tag => raise InvalidTag(field="manifest.target.architecture", tag~)
  }
}

///|
fn write_os_abi(writer : ByteWriter, value : OperatingSystemAbi) -> Unit {
  writer.write_u8(
    match value {
      MacOS => 0
      LinuxGnu => 1
      LinuxMusl => 2
      WindowsMsvc => 3
    },
  )
}

///|
fn read_os_abi(
  reader : ByteReader,
) -> OperatingSystemAbi raise ArtifactDecodeError {
  match reader.read_u8("manifest.target.operating_system_abi") {
    0 => MacOS
    1 => LinuxGnu
    2 => LinuxMusl
    3 => WindowsMsvc
    tag => raise InvalidTag(field="manifest.target.operating_system_abi", tag~)
  }
}

///|
fn write_endianness(writer : ByteWriter, value : @machv.Endianness) -> Unit {
  writer.write_u8(
    match value {
      Little => 0
      Big => 1
    },
  )
}

///|
fn read_endianness(
  reader : ByteReader,
) -> @machv.Endianness raise ArtifactDecodeError {
  match reader.read_u8("manifest.target.endianness") {
    0 => Little
    1 => Big
    tag => raise InvalidTag(field="manifest.target.endianness", tag~)
  }
}

///|
fn write_pointer_width(writer : ByteWriter, value : PointerWidth) -> Unit {
  match value {
    Bits64 => writer.write_u8(0)
  }
}

///|
fn read_pointer_width(
  reader : ByteReader,
) -> PointerWidth raise ArtifactDecodeError {
  match reader.read_u8("manifest.target.pointer_width") {
    0 => Bits64
    tag => raise InvalidTag(field="manifest.target.pointer_width", tag~)
  }
}

///|
fn write_optimization_level(
  writer : ByteWriter,
  value : OptimizationLevel,
) -> Unit {
  writer.write_u8(
    match value {
      O0 => 0
      O1 => 1
      O2 => 2
      O3 => 3
    },
  )
}

///|
fn read_optimization_level(
  reader : ByteReader,
) -> OptimizationLevel raise ArtifactDecodeError {
  match reader.read_u8("manifest.policy.optimization_level") {
    0 => O0
    1 => O1
    2 => O2
    3 => O3
    tag => raise InvalidTag(field="manifest.policy.optimization_level", tag~)
  }
}

///|
fn write_code_model(writer : ByteWriter, value : CodeModel) -> Unit {
  writer.write_u8(
    match value {
      Small => 0
      Large => 1
    },
  )
}

///|
fn read_code_model(reader : ByteReader) -> CodeModel raise ArtifactDecodeError {
  match reader.read_u8("manifest.policy.code_model") {
    0 => Small
    1 => Large
    tag => raise InvalidTag(field="manifest.policy.code_model", tag~)
  }
}

///|
fn write_call_binding(writer : ByteWriter, value : CallBinding) -> Unit {
  writer.write_u8(
    match value {
      StableEntry => 0
      DirectBody => 1
    },
  )
}

///|
fn read_call_binding(
  reader : ByteReader,
) -> CallBinding raise ArtifactDecodeError {
  match reader.read_u8("manifest.policy.call_binding") {
    0 => StableEntry
    1 => DirectBody
    tag => raise InvalidTag(field="manifest.policy.call_binding", tag~)
  }
}

///|
fn write_value_type(writer : ByteWriter, value : @machv.ValueType) -> Unit {
  writer.write_u8(
    match value {
      I32 => 0
      I64 => 1
      F32 => 2
      F64 => 3
      V128 => 4
      Ptr64 => 5
      GcRef64 => 6
    },
  )
}

///|
fn read_value_type(
  reader : ByteReader,
  field : String,
) -> @machv.ValueType raise ArtifactDecodeError {
  match reader.read_u8(field) {
    0 => I32
    1 => I64
    2 => F32
    3 => F64
    4 => V128
    5 => Ptr64
    6 => GcRef64
    tag => raise InvalidTag(field~, tag~)
  }
}

///|
fn write_signature(
  writer : ByteWriter,
  signature : @machv.Signature,
  field : String,
) -> Unit raise ArtifactEncodeError {
  writer.write_u32(signature.params.length(), field + ".params.length")
  for value in signature.params {
    write_value_type(writer, value)
  }
  writer.write_u32(signature.results.length(), field + ".results.length")
  for value in signature.results {
    write_value_type(writer, value)
  }
}

///|
fn read_signature(
  reader : ByteReader,
  field : String,
) -> @machv.Signature raise ArtifactDecodeError {
  let params_count = reader.read_count(
    field + ".params.length",
    reader.limits.max_signature_values,
  )
  let params : Array[@machv.ValueType] = []
  for _ in 0.. Unit raise ArtifactEncodeError {
  writer.write_string(source.file, field + ".file")
  writer.write_u32(source.line, field + ".line")
  writer.write_u32(source.column, field + ".column")
}

///|
fn read_source(
  reader : ByteReader,
  field : String,
) -> @machv.SourceLocation raise ArtifactDecodeError {
  @machv.SourceLocation::new(
    reader.read_string(field + ".file"),
    reader.read_u32(field + ".line"),
    reader.read_u32(field + ".column"),
  )
}

///|
fn write_optional_source(
  writer : ByteWriter,
  source : @machv.SourceLocation?,
  field : String,
) -> Unit raise ArtifactEncodeError {
  match source {
    None => writer.write_bool(false)
    Some(value) => {
      writer.write_bool(true)
      write_source(writer, value, field)
    }
  }
}

///|
fn read_optional_source(
  reader : ByteReader,
  field : String,
) -> @machv.SourceLocation? raise ArtifactDecodeError {
  if reader.read_bool(field + ".present") {
    Some(read_source(reader, field))
  } else {
    None
  }
}

///|
fn write_relocation_kind(
  writer : ByteWriter,
  kind : @code_object.RelocationKind,
) -> Unit {
  writer.write_u8(
    match kind {
      AArch64Call26 => 0
      AArch64Jump26 => 1
      AArch64Page21 => 2
      AArch64PageOffset12 => 3
      X64PcRelative32 => 4
      Absolute64 => 5
    },
  )
}

///|
fn read_relocation_kind(
  reader : ByteReader,
) -> @code_object.RelocationKind raise ArtifactDecodeError {
  match reader.read_u8("function.relocation.kind") {
    0 => AArch64Call26
    1 => AArch64Jump26
    2 => AArch64Page21
    3 => AArch64PageOffset12
    4 => X64PcRelative32
    5 => Absolute64
    tag => raise InvalidTag(field="function.relocation.kind", tag~)
  }
}

///|
fn write_relocation_target(
  writer : ByteWriter,
  target : @code_object.RelocationTarget,
) -> Unit raise ArtifactEncodeError {
  match target {
    Code(symbol) => {
      writer.write_u8(0)
      writer.write_string(symbol.name, "function.relocation.target.code")
    }
    External(symbol) => {
      writer.write_u8(1)
      writer.write_string(symbol.name, "function.relocation.target.external")
    }
    Data(symbol) => {
      writer.write_u8(2)
      writer.write_string(symbol.name, "function.relocation.target.data")
    }
  }
}

///|
fn read_relocation_target(
  reader : ByteReader,
) -> @code_object.RelocationTarget raise ArtifactDecodeError {
  match reader.read_u8("function.relocation.target") {
    0 =>
      Code(
        @machv.CodeSymbol::new(
          reader.read_string("function.relocation.target.code"),
        ),
      )
    1 =>
      External(
        @machv.ExternalSymbol::new(
          reader.read_string("function.relocation.target.external"),
        ),
      )
    2 =>
      Data(
        @machv.DataSymbol::new(
          reader.read_string("function.relocation.target.data"),
        ),
      )
    tag => raise InvalidTag(field="function.relocation.target", tag~)
  }
}

///|
fn write_trap_reason(writer : ByteWriter, reason : @machv.TrapReason) -> Unit {
  match reason {
    Unreachable => writer.write_u8(0)
    IntegerDivisionByZero => writer.write_u8(1)
    IntegerOverflow => writer.write_u8(2)
    InvalidConversionToInteger => writer.write_u8(3)
    MemoryOutOfBounds => writer.write_u8(4)
    TableOutOfBounds => writer.write_u8(5)
    IndirectCallTypeMismatch => writer.write_u8(6)
    NullReference => writer.write_u8(7)
    UnalignedAtomic => writer.write_u8(8)
    UnsupportedOperation => writer.write_u8(9)
    StackOverflow => writer.write_u8(10)
    User(value) => {
      writer.write_u8(11)
      writer.write_i64(value.to_int64())
    }
  }
}

///|
fn read_trap_reason(
  reader : ByteReader,
) -> @machv.TrapReason raise ArtifactDecodeError {
  match reader.read_u8("function.trap.reason") {
    0 => Unreachable
    1 => IntegerDivisionByZero
    2 => IntegerOverflow
    3 => InvalidConversionToInteger
    4 => MemoryOutOfBounds
    5 => TableOutOfBounds
    6 => IndirectCallTypeMismatch
    7 => NullReference
    8 => UnalignedAtomic
    9 => UnsupportedOperation
    10 => StackOverflow
    11 => User(reader.read_int_from_i64("function.trap.reason.user"))
    tag => raise InvalidTag(field="function.trap.reason", tag~)
  }
}

///|
fn write_safepoint_kind(
  writer : ByteWriter,
  kind : @machv.SafepointKind,
) -> Unit {
  writer.write_u8(
    match kind {
      Gc => 0
      Cancellation => 1
      GcAndCancellation => 2
    },
  )
}

///|
fn read_safepoint_kind(
  reader : ByteReader,
) -> @machv.SafepointKind raise ArtifactDecodeError {
  match reader.read_u8("function.safepoint.kind") {
    0 => Gc
    1 => Cancellation
    2 => GcAndCancellation
    tag => raise InvalidTag(field="function.safepoint.kind", tag~)
  }
}

///|
fn write_register_bank(
  writer : ByteWriter,
  bank : @code_object.RegisterBank,
) -> Unit {
  writer.write_u8(
    match bank {
      Int => 0
      FpVector => 1
    },
  )
}

///|
fn read_register_bank(
  reader : ByteReader,
  field : String,
) -> @code_object.RegisterBank raise ArtifactDecodeError {
  match reader.read_u8(field) {
    0 => Int
    1 => FpVector
    tag => raise InvalidTag(field~, tag~)
  }
}

///|
fn write_root_location(
  writer : ByteWriter,
  root : @code_object.RootLocation,
) -> Unit raise ArtifactEncodeError {
  match root {
    Register(bank, index) => {
      writer.write_u8(0)
      write_register_bank(writer, bank)
      writer.write_u32(index, "function.safepoint.root.register")
    }
    Stack(offset~, ty~) => {
      writer.write_u8(1)
      writer.write_i64(offset.to_int64())
      write_value_type(writer, ty)
    }
  }
}

///|
fn read_root_location(
  reader : ByteReader,
) -> @code_object.RootLocation raise ArtifactDecodeError {
  match reader.read_u8("function.safepoint.root") {
    0 =>
      Register(
        read_register_bank(reader, "function.safepoint.root.register_bank"),
        reader.read_u32("function.safepoint.root.register"),
      )
    1 =>
      Stack(
        offset=reader.read_int_from_i64("function.safepoint.root.stack_offset"),
        ty=read_value_type(reader, "function.safepoint.root.stack_type"),
      )
    tag => raise InvalidTag(field="function.safepoint.root", tag~)
  }
}

///|
fn write_unwind_operation(
  writer : ByteWriter,
  operation : @code_object.UnwindOperation,
) -> Unit raise ArtifactEncodeError {
  match operation {
    StackAlloc(size~) => {
      writer.write_u8(0)
      writer.write_u32(size, "function.unwind.stack_size")
    }
    SetFramePointer(bank, index, cfa_offset~) => {
      writer.write_u8(1)
      write_register_bank(writer, bank)
      writer.write_u32(index, "function.unwind.register")
      writer.write_i64(cfa_offset.to_int64())
    }
    SaveRegister(bank, index, cfa_offset~) => {
      writer.write_u8(2)
      write_register_bank(writer, bank)
      writer.write_u32(index, "function.unwind.register")
      writer.write_i64(cfa_offset.to_int64())
    }
  }
}

///|
fn read_unwind_operation(
  reader : ByteReader,
) -> @code_object.UnwindOperation raise ArtifactDecodeError {
  match reader.read_u8("function.unwind.operation") {
    0 => StackAlloc(size=reader.read_u32("function.unwind.stack_size"))
    1 =>
      SetFramePointer(
        read_register_bank(reader, "function.unwind.register_bank"),
        reader.read_u32("function.unwind.register"),
        cfa_offset=reader.read_int_from_i64("function.unwind.cfa_offset"),
      )
    2 =>
      SaveRegister(
        read_register_bank(reader, "function.unwind.register_bank"),
        reader.read_u32("function.unwind.register"),
        cfa_offset=reader.read_int_from_i64("function.unwind.cfa_offset"),
      )
    tag => raise InvalidTag(field="function.unwind.operation", tag~)
  }
}

///|
fn write_digest(
  writer : ByteWriter,
  digest : Digest,
  field : String,
) -> Unit raise ArtifactEncodeError {
  if digest.bytes.length() != 32 {
    raise InvalidArtifactField(
      field~,
      message="expected exactly 32 SHA-256 bytes",
    )
  }
  writer.write_bytes(digest.bytes)
}

///|
fn read_digest(
  reader : ByteReader,
  field : String,
) -> Digest raise ArtifactDecodeError {
  { bytes: reader.read_raw(32, field) }
}

///|
fn write_manifest(
  writer : ByteWriter,
  manifest : CompatibilityManifest,
) -> Unit raise ArtifactEncodeError {
  if manifest.format_version != FORMAT_VERSION {
    raise InvalidArtifactField(
      field="manifest.format_version",
      message="expected version \{FORMAT_VERSION}",
    )
  }
  writer.write_u32(manifest.format_version, "manifest.format_version")
  write_architecture(writer, manifest.target.architecture)
  write_os_abi(writer, manifest.target.operating_system_abi)
  write_endianness(writer, manifest.target.endianness)
  write_pointer_width(writer, manifest.target.pointer_width)
  writer.write_u32(
    manifest.required_cpu_features.length(),
    "manifest.required_cpu_features.length",
  )
  for feature in manifest.required_cpu_features {
    writer.write_string(feature.name, "manifest.required_cpu_features.name")
  }
  writer.write_u32(manifest.jit_abi_version, "manifest.jit_abi_version")
  writer.write_string(manifest.codegen_revision, "manifest.codegen_revision")
  writer.write_string(
    manifest.module_identity.name,
    "manifest.module_identity.name",
  )
  write_digest(
    writer,
    manifest.module_identity.source_digest,
    "manifest.module_identity.source_digest",
  )
  write_digest(
    writer,
    manifest.module_identity.semantic_features_digest,
    "manifest.module_identity.semantic_features_digest",
  )
  write_optimization_level(writer, manifest.policy.optimization_level)
  write_code_model(writer, manifest.policy.code_model)
  write_call_binding(writer, manifest.policy.call_binding)
  writer.write_bool(manifest.policy.cancellation_safepoints)
  writer.write_bool(manifest.policy.debug_info)
}

///|
fn read_manifest(
  reader : ByteReader,
) -> CompatibilityManifest raise ArtifactDecodeError {
  let format_version = reader.read_u32("manifest.format_version")
  if format_version != FORMAT_VERSION {
    raise UnsupportedVersion(version=format_version)
  }
  let target : TargetSpec = {
    architecture: read_architecture(reader),
    operating_system_abi: read_os_abi(reader),
    endianness: read_endianness(reader),
    pointer_width: read_pointer_width(reader),
  }
  let feature_count = reader.read_count(
    "manifest.required_cpu_features.length",
    reader.limits.max_cpu_features,
  )
  let required_cpu_features : Array[CpuFeature] = []
  for _ in 0.. Unit raise ArtifactEncodeError {
  writer.write_u32(import_.function_index, "import.function_index")
  writer.write_string(import_.module_name, "import.module_name")
  writer.write_string(import_.function_name, "import.function_name")
  writer.write_string(import_.symbol.name, "import.symbol")
  write_signature(writer, import_.signature, "import.signature")
}

///|
fn read_import(reader : ByteReader) -> ImportIdentity raise ArtifactDecodeError {
  {
    function_index: reader.read_u32("import.function_index"),
    module_name: reader.read_string("import.module_name"),
    function_name: reader.read_string("import.function_name"),
    symbol: @machv.ExternalSymbol::new(reader.read_string("import.symbol")),
    signature: read_signature(reader, "import.signature"),
  }
}

///|
fn write_function(
  writer : ByteWriter,
  function : FunctionCode,
) -> Unit raise ArtifactEncodeError {
  writer.write_u32(
    function.identity.function_index,
    "function.identity.function_index",
  )
  writer.write_string(function.identity.symbol.name, "function.identity.symbol")
  write_signature(
    writer,
    function.identity.signature,
    "function.identity.signature",
  )
  writer.write_u32(function.entry_offset, "function.entry_offset")
  writer.write_u32(function.frame_size, "function.frame_size")
  writer.write_u32(function.code.length(), "function.code.length")
  writer.write_byte_array(function.code)
  writer.write_u32(function.relocations.length(), "function.relocations.length")
  for relocation in function.relocations {
    writer.write_u32(relocation.offset, "function.relocation.offset")
    write_relocation_kind(writer, relocation.kind)
    write_relocation_target(writer, relocation.target)
    writer.write_i64(relocation.addend)
  }
  writer.write_u32(function.sources.length(), "function.sources.length")
  for site in function.sources {
    writer.write_u32(site.offset, "function.source.offset")
    write_source(writer, site.source, "function.source")
  }
  writer.write_u32(function.traps.length(), "function.traps.length")
  for site in function.traps {
    writer.write_u32(site.offset, "function.trap.offset")
    write_trap_reason(writer, site.reason)
    write_optional_source(writer, site.source, "function.trap.source")
  }
  writer.write_u32(function.safepoints.length(), "function.safepoints.length")
  for site in function.safepoints {
    writer.write_u32(site.offset, "function.safepoint.offset")
    write_safepoint_kind(writer, site.kind)
    writer.write_u32(site.roots.length(), "function.safepoint.roots.length")
    for root in site.roots {
      write_root_location(writer, root)
    }
    write_optional_source(writer, site.source, "function.safepoint.source")
    match site.stack_map {
      None => writer.write_bool(false)
      Some(stack_map) => {
        writer.write_bool(true)
        writer.write_u32(stack_map.id, "function.safepoint.stack_map.id")
        writer.write_u32(
          stack_map.argument_root_count,
          "function.safepoint.stack_map.argument_root_count",
        )
      }
    }
  }
  writer.write_u32(function.unwind.length(), "function.unwind.length")
  for directive in function.unwind {
    writer.write_u32(directive.offset, "function.unwind.offset")
    write_unwind_operation(writer, directive.operation)
  }
}

///|
fn read_function(
  reader : ByteReader,
  architecture : @code_object.Architecture,
  total_code_bytes : Ref[Int],
) -> FunctionCode raise ArtifactDecodeError {
  let identity : FunctionIdentity = {
    function_index: reader.read_u32("function.identity.function_index"),
    symbol: @machv.CodeSymbol::new(
      reader.read_string("function.identity.symbol"),
    ),
    signature: read_signature(reader, "function.identity.signature"),
  }
  let entry_offset = reader.read_u32("function.entry_offset")
  let frame_size = reader.read_u32("function.frame_size")
  let code_count = reader.read_count(
    "function.code.length",
    reader.limits.max_code_bytes_per_function,
  )
  if code_count > reader.limits.max_total_code_bytes - total_code_bytes.val {
    raise LimitExceeded(
      field="artifact.total_code_bytes",
      count=total_code_bytes.val + code_count,
      limit=reader.limits.max_total_code_bytes,
    )
  }
  total_code_bytes.val += code_count
  let code = bytes_to_array(reader.read_raw(code_count, "function.code"))
  let entry_alignment = if architecture == AArch64 { 4 } else { 1 }
  if entry_offset >= code_count || entry_offset % entry_alignment != 0 {
    raise InvalidValue(
      field="function.entry_offset",
      message="offset is outside code or violates target instruction alignment",
    )
  }
  let relocation_count = reader.read_count(
    "function.relocations.length",
    reader.limits.max_relocations_per_function,
  )
  let relocations : Array[@code_object.Relocation] = []
  for _ in 0.. Bytes raise ArtifactEncodeError {
  let writer = ByteWriter::new()
  writer.write_byte_array([b'c', b'w', b'a', b's'])
  write_manifest(writer, artifact.manifest)
  writer.write_u32(artifact.imports.length(), "artifact.imports.length")
  for import_ in artifact.imports {
    write_import(writer, import_)
  }
  writer.write_u32(artifact.functions.length(), "artifact.functions.length")
  for function in artifact.functions {
    write_function(writer, function)
  }
  writer.finish()
}

///|
/// Decode untrusted v9 Cwasm bytes into ordinary artifact data.
///
/// This function performs bounded structural decoding only. The native
/// `wasmoon_jit` facade performs compatibility and code-object verification
/// before installation.
pub fn decode(
  data : Bytes,
  limits? : DecodeLimits = DecodeLimits::default(),
) -> Artifact raise ArtifactDecodeError {
  if data.length() > limits.max_artifact_bytes {
    raise ArtifactTooLarge(size=data.length(), limit=limits.max_artifact_bytes)
  }
  let reader = ByteReader::new(data, limits)
  let magic = reader.read_raw(4, "magic")
  if magic != Bytes::from_array([b'c', b'w', b'a', b's']) {
    raise BadMagic
  }
  let manifest = read_manifest(reader)
  let import_count = reader.read_count(
    "artifact.imports.length",
    limits.max_imports,
  )
  let imports : Array[ImportIdentity] = []
  for _ in 0..