// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.

///|
priv enum CompilerFactTruth {
  FactProvenTrue
  FactProvenFalse
  FactUnknown
} derive(Eq)

///|
priv struct CompilerFactIntegerDomain {
  width : @representation.IntWidth
  exact_bits : UInt64?
  unsigned_min : UInt64?
  unsigned_max : UInt64?
  signed_min : Int64?
  signed_max : Int64?
  nonzero : Bool
}

///|
fn compiler_fact_query_expected_integer_width(
  expected : @lib.ValType,
) -> @representation.IntWidth? {
  if expected == @lib.ValType::i32() {
    Some(@representation.IntWidth::i32())
  } else if expected == @lib.ValType::i64() {
    Some(@representation.IntWidth::i64())
  } else {
    None
  }
}

///|
fn compiler_fact_query_unsigned_width_max(
  width : @representation.IntWidth,
) -> UInt64 {
  match width {
    @representation.Int8 => 0xFFUL
    @representation.Int16 => 0xFFFFUL
    @representation.Int32 => 0xFFFF_FFFFUL
    @representation.Int64 => UInt64::lnot(0UL)
  }
}

///|
fn compiler_fact_query_signed_width_bounds(
  width : @representation.IntWidth,
) -> (Int64, Int64) {
  match width {
    @representation.Int8 => (-128L, 127L)
    @representation.Int16 => (-32768L, 32767L)
    @representation.Int32 => (-2147483648L, 2147483647L)
    @representation.Int64 => (-9223372036854775807L - 1L, 9223372036854775807L)
  }
}

///|
fn compiler_fact_query_signed_bits(
  width : @representation.IntWidth,
  bits : UInt64,
) -> Int64 {
  match width {
    @representation.Int32 => bits.to_uint().reinterpret_as_int().to_int64()
    @representation.Int64 => bits.reinterpret_as_int64()
    @representation.Int8 | @representation.Int16 => bits.to_int64()
  }
}

///|
fn compiler_fact_query_sign_bit(width : @representation.IntWidth) -> UInt64 {
  match width {
    @representation.Int8 => 0x80UL
    @representation.Int16 => 0x8000UL
    @representation.Int32 => 0x8000_0000UL
    @representation.Int64 => 0x8000_0000_0000_0000UL
  }
}

///|
fn compiler_fact_query_ranges_overlap(
  left_min : Int64,
  left_max : Int64,
  right_min : Int64,
  right_max : Int64,
) -> Bool {
  left_min <= right_max && right_min <= left_max
}

///|
fn compiler_fact_query_signed_unsigned_ranges_overlap(
  width : @representation.IntWidth,
  unsigned_min : UInt64,
  unsigned_max : UInt64,
  signed_min : Int64,
  signed_max : Int64,
) -> Bool {
  let sign_bit = compiler_fact_query_sign_bit(width)
  if unsigned_max < sign_bit {
    return compiler_fact_query_ranges_overlap(
      unsigned_min.to_int64(),
      unsigned_max.to_int64(),
      signed_min,
      signed_max,
    )
  }
  if unsigned_min >= sign_bit {
    return compiler_fact_query_ranges_overlap(
      compiler_fact_query_signed_bits(width, unsigned_min),
      compiler_fact_query_signed_bits(width, unsigned_max),
      signed_min,
      signed_max,
    )
  }
  let (width_min, width_max) = compiler_fact_query_signed_width_bounds(width)
  compiler_fact_query_ranges_overlap(
    unsigned_min.to_int64(),
    width_max,
    signed_min,
    signed_max,
  ) ||
  compiler_fact_query_ranges_overlap(
    width_min,
    compiler_fact_query_signed_bits(width, unsigned_max),
    signed_min,
    signed_max,
  )
}

///|
fn compiler_fact_integer_domain_exact(
  width : @representation.IntWidth,
  bits : UInt64,
) -> CompilerFactIntegerDomain {
  let signed = compiler_fact_query_signed_bits(width, bits)
  {
    width,
    exact_bits: Some(bits),
    unsigned_min: Some(bits),
    unsigned_max: Some(bits),
    signed_min: Some(signed),
    signed_max: Some(signed),
    nonzero: bits != 0UL,
  }
}

///|
fn compiler_fact_integer_domain_from_const(
  instruction : @lib.Instruction,
) -> CompilerFactIntegerDomain? {
  match instruction {
    @lib.I32Const(@lib.I32(value)) =>
      Some(
        compiler_fact_integer_domain_exact(
          @representation.IntWidth::i32(),
          value.reinterpret_as_uint().to_uint64(),
        ),
      )
    @lib.I64Const(@lib.I64(value)) =>
      Some(
        compiler_fact_integer_domain_exact(
          @representation.IntWidth::i64(),
          value.reinterpret_as_uint64(),
        ),
      )
    _ => None
  }
}

///|
fn compiler_fact_query_fact_heap_type_indices_valid(
  heap : @representation.FactHeapType,
  env : @validate.Env,
) -> Bool {
  match heap {
    @representation.TypeIndex(index) =>
      index < env.global_types.length().reinterpret_as_uint()
    _ => true
  }
}

///|
fn compiler_fact_query_invalid_effect_indices(
  effects : @representation.EffectFacts,
  env : @validate.Env,
) -> Int32 {
  let mut invalid = 0
  let check_globals = fn(set : @representation.ClosedGlobalSet?) -> Unit {
    match set {
      Some(@representation.ClosedGlobalSet(values)) =>
        for value in values {
          if env.get_global_type(@lib.GlobalIdx::new(value)) is None {
            invalid += 1
          }
        }
      None => ()
    }
  }
  let check_memories = fn(set : @representation.ClosedMemorySet?) -> Unit {
    match set {
      Some(@representation.ClosedMemorySet(values)) =>
        for value in values {
          if env.get_memtype(@lib.MemIdx::new(value)) is None {
            invalid += 1
          }
        }
      None => ()
    }
  }
  let check_tables = fn(set : @representation.ClosedTableSet?) -> Unit {
    match set {
      Some(@representation.ClosedTableSet(values)) =>
        for value in values {
          if env.get_table_type(@lib.TableIdx::new(value)) is None {
            invalid += 1
          }
        }
      None => ()
    }
  }
  let check_types = fn(set : @representation.ClosedTypeViewSet?) -> Unit {
    match set {
      Some(@representation.ClosedTypeViewSet(values)) =>
        for value in values {
          if value >= env.global_types.length().reinterpret_as_uint() {
            invalid += 1
          }
        }
      None => ()
    }
  }
  let check_functions = fn(set : @representation.ClosedFunctionSet?) -> Unit {
    match set {
      Some(@representation.ClosedFunctionSet(values)) =>
        for value in values {
          if !env.has_func(@lib.FuncIdx::new(value)) {
            invalid += 1
          }
        }
      None => ()
    }
  }
  check_globals(effects.read_globals)
  check_globals(effects.written_globals)
  check_memories(effects.read_memories)
  check_memories(effects.written_memories)
  check_tables(effects.read_tables)
  check_tables(effects.written_tables)
  check_types(effects.read_heap_types)
  check_types(effects.written_heap_types)
  check_functions(effects.direct_callees)
  invalid
}

///|
fn compiler_fact_query_invalid_resource_indices(
  facts : @representation.OptimizationFactsSec,
  env : @validate.Env,
) -> Int32 {
  let mut invalid = 0
  for function in facts.functions {
    match function.effects {
      Some(effects) =>
        invalid += compiler_fact_query_invalid_effect_indices(effects, env)
      None => ()
    }
  }
  invalid
}

///|
fn compiler_fact_query_value_indices_valid(
  facts : @representation.ValueFacts,
  env : @validate.Env,
) -> Bool {
  match facts.possible_values {
    Some(@representation.ClosedValueSet(values)) =>
      for value in values {
        match value {
          @representation.RefFuncValue(function) =>
            if !env.has_func(@lib.FuncIdx::new(function)) {
              return false
            }
          @representation.ImmutableGlobalValue(global) =>
            if env.get_global_type(@lib.GlobalIdx::new(global)) is None {
              return false
            }
          @representation.RefNullValue(heap) =>
            if !compiler_fact_query_fact_heap_type_indices_valid(heap, env) {
              return false
            }
          _ => ()
        }
      }
    None => ()
  }
  match facts.reference {
    Some(reference) => {
      match reference.heap {
        Some(@representation.ExactRuntimeType(heap))
        | Some(@representation.SubtypesOf(heap)) =>
          if !compiler_fact_query_fact_heap_type_indices_valid(heap, env) {
            return false
          }
        Some(
          @representation.ClosedRuntimeTypes(
            @representation.ClosedHeapTypeSet(types)
          )
        ) =>
          for heap in types {
            if !compiler_fact_query_fact_heap_type_indices_valid(heap, env) {
              return false
            }
          }
        None => ()
      }
      match reference.function_targets {
        Some(@representation.ClosedFunctionSet(functions)) =>
          for function in functions {
            if !env.has_func(@lib.FuncIdx::new(function)) {
              return false
            }
          }
        None => ()
      }
    }
    None => ()
  }
  true
}

///|
fn compiler_fact_query_result_type(
  instruction : @lib.Instruction,
  result : UInt32,
  env : @validate.Env,
) -> @lib.ValType? {
  let result_slot = result.reinterpret_as_int()
  match instruction {
    @lib.Call(function) =>
      match env.get_functype_by_funcidx(function) {
        Some(@lib.FuncType(_, results)) => results.get(result_slot)
        _ => None
      }
    @lib.CallIndirect(type_, _) | @lib.CallRef(type_) =>
      match env.resolve_functype(type_) {
        Some(@lib.FuncType(_, results)) => results.get(result_slot)
        _ => None
      }
    @lib.Block(type_, _)
    | @lib.Loop(type_, _)
    | @lib.If(type_, _, _)
    | @lib.Try(type_, _, _, _)
    | @lib.TryTable(type_, _, _) =>
      match env.expand_blocktype(type_) {
        Ok((_, results)) => results.get(result_slot)
        Err(_) => None
      }
    @lib.I32Const(_) if result == 0U => Some(@lib.ValType::i32())
    @lib.I64Const(_) if result == 0U => Some(@lib.ValType::i64())
    @lib.F32Const(_) if result == 0U => Some(@lib.ValType::f32())
    @lib.F64Const(_) if result == 0U => Some(@lib.ValType::f64())
    @lib.RefNull(type_) if result == 0U => Some(@lib.ValType::ref_type(type_))
    @lib.RefFunc(_) if result == 0U => Some(@lib.ValType::funcref())
    _ => None
  }
}

///|
priv struct CompilerFactQuery {
  version : UInt32
  policy : CompilerFactUsePolicy
  index : CompilerFactIndex
  env : @validate.Env
  sites : Array[Array[(UInt32, @lib.Instruction)]]
  imported_functions : Int32
  stats : CompilerFactMaterializationStats
}

///|
fn CompilerFactQuery::new(
  module_ : @lib.Module,
  version : UInt32,
  policy : CompilerFactUsePolicy,
  index : CompilerFactIndex,
  sites : Array[Array[(UInt32, @lib.Instruction)]],
  stats : CompilerFactMaterializationStats,
) -> CompilerFactQuery {
  {
    version,
    policy,
    index,
    env: @validate.Env::new().with_module(module_),
    sites,
    imported_functions: directize_imported_func_count(module_),
    stats,
  }
}

///|
fn CompilerFactQuery::site_resolves(
  self : CompilerFactQuery,
  site : @representation.CodeSite,
  instruction : @lib.Instruction,
) -> Bool {
  let defined = site.function.reinterpret_as_int() - self.imported_functions
  if defined < 0 || defined >= self.sites.length() {
    self.stats.stale_or_unresolved_sites += 1
    return false
  }
  let candidates = self.sites[defined]
  // The production encoder emits opcode sites in increasing byte-offset order.
  // Lower bound keeps the old first-match behavior if an invalid input repeats
  // an offset.
  let mut lo = 0
  let mut hi = candidates.length()
  while lo < hi {
    let mid = lo + (hi - lo) / 2
    if candidates[mid].0 < site.offset {
      lo = mid + 1
    } else {
      hi = mid
    }
  }
  if lo < candidates.length() && candidates[lo].0 == site.offset {
    if candidates[lo].1 == instruction {
      return true
    }
    self.stats.stale_or_unresolved_sites += 1
    return false
  }
  self.stats.stale_or_unresolved_sites += 1
  false
}

///|
fn CompilerFactQuery::value_facts(
  self : CompilerFactQuery,
  function : UInt32,
  site : @representation.ValueSite,
  instruction : @lib.Instruction,
  expected : @lib.ValType,
) -> @representation.ValueFacts? {
  if self.version != 1U {
    self.stats.unsupported_versions += 1
    return None
  }
  if self.policy != CompilerFactUsePolicy::trust_assertions() {
    self.stats.facts_blocked_by_trust += 1
    return None
  }
  if site.site.function != function ||
    !self.site_resolves(site.site, instruction) {
    return None
  }
  match compiler_fact_query_result_type(instruction, site.result, self.env) {
    Some(actual) if actual == expected => ()
    _ => {
      self.stats.type_mismatches += 1
      return None
    }
  }
  let facts = match self.index.value_facts(site) {
    Some(facts) => facts
    None => {
      self.stats.stale_or_unresolved_sites += 1
      return None
    }
  }
  if !compiler_fact_query_value_indices_valid(facts, self.env) {
    self.stats.invalid_referenced_indices += 1
    return None
  }
  Some(facts)
}

///|
fn compiler_fact_query_integer_domain_from_facts(
  facts : @representation.ValueFacts,
  expected : @lib.ValType,
  stats : CompilerFactMaterializationStats,
) -> CompilerFactIntegerDomain? {
  let width = match compiler_fact_query_expected_integer_width(expected) {
    Some(width) => width
    None => {
      stats.type_mismatches += 1
      return None
    }
  }
  let mut exact_bits : UInt64? = None
  let mut nonzero = false
  match facts.possible_values {
    Some(
      @representation.ClosedValueSet([@representation.IntegerValue(constant)])
    ) => {
      if constant.width != width ||
        (
          constant.bits &
          UInt64::lnot(compiler_fact_query_unsigned_width_max(width))
        ) !=
        0UL {
        stats.type_mismatches += 1
        return None
      }
      exact_bits = Some(constant.bits)
      nonzero = constant.bits != 0UL
    }
    Some(@representation.ClosedValueSet([])) => {
      stats.malformed_or_impossible_facts += 1
      return None
    }
    Some(@representation.ClosedValueSet(values)) => {
      if values.any(fn(value) { value is @representation.IntegerValue(_) }) {
        stats.valid_facts_without_rewrite += 1
      } else {
        stats.type_mismatches += 1
      }
      return None
    }
    None => ()
  }
  let mut unsigned_min : UInt64? = None
  let mut unsigned_max : UInt64? = None
  let mut signed_min : Int64? = None
  let mut signed_max : Int64? = None
  match exact_bits {
    Some(bits) => {
      unsigned_min = Some(bits)
      unsigned_max = Some(bits)
      let signed = compiler_fact_query_signed_bits(width, bits)
      signed_min = Some(signed)
      signed_max = Some(signed)
    }
    None => ()
  }
  match facts.integer {
    Some(integer) => {
      if integer.width != width {
        stats.type_mismatches += 1
        return None
      }
      match integer.unsigned_range {
        Some(range) if range.min <= range.max &&
          range.max <= compiler_fact_query_unsigned_width_max(width) => {
          unsigned_min = Some(range.min)
          unsigned_max = Some(range.max)
        }
        Some(_) => {
          stats.malformed_or_impossible_facts += 1
          return None
        }
        None => ()
      }
      match integer.signed_range {
        Some(range) => {
          let (width_min, width_max) = compiler_fact_query_signed_width_bounds(
            width,
          )
          if range.min > range.max ||
            range.min < width_min ||
            range.max > width_max {
            stats.malformed_or_impossible_facts += 1
            return None
          }
          signed_min = Some(range.min)
          signed_max = Some(range.max)
        }
        None => ()
      }
      if integer.nonzero {
        nonzero = true
        match (unsigned_min, unsigned_max) {
          (Some(0UL), Some(0UL)) => {
            stats.malformed_or_impossible_facts += 1
            return None
          }
          _ => ()
        }
        match (signed_min, signed_max) {
          (Some(0L), Some(0L)) => {
            stats.malformed_or_impossible_facts += 1
            return None
          }
          _ => ()
        }
      }
    }
    None => ()
  }
  match (unsigned_min, unsigned_max) {
    (Some(min), Some(_)) if min > 0UL => nonzero = true
    _ => ()
  }
  match (signed_min, signed_max) {
    (Some(min), Some(max)) if min > 0L || max < 0L => nonzero = true
    _ => ()
  }
  match (unsigned_min, unsigned_max, signed_min, signed_max) {
    (Some(umin), Some(umax), Some(smin), Some(smax)) =>
      if !compiler_fact_query_signed_unsigned_ranges_overlap(
          width, umin, umax, smin, smax,
        ) {
        stats.malformed_or_impossible_facts += 1
        return None
      }
    _ => ()
  }
  match exact_bits {
    Some(bits) => {
      match (unsigned_min, unsigned_max) {
        (Some(min), Some(max)) if bits < min || bits > max => {
          stats.malformed_or_impossible_facts += 1
          return None
        }
        _ => ()
      }
      let signed = compiler_fact_query_signed_bits(width, bits)
      match (signed_min, signed_max) {
        (Some(min), Some(max)) if signed < min || signed > max => {
          stats.malformed_or_impossible_facts += 1
          return None
        }
        _ => ()
      }
    }
    None => ()
  }
  if exact_bits is None &&
    unsigned_min is None &&
    signed_min is None &&
    !nonzero {
    return None
  }
  {
    width,
    exact_bits,
    unsigned_min,
    unsigned_max,
    signed_min,
    signed_max,
    nonzero,
  }
  |> Some
}

///|
fn CompilerFactQuery::integer_domain(
  self : CompilerFactQuery,
  function : UInt32,
  site : @representation.ValueSite,
  instruction : @lib.Instruction,
  expected : @lib.ValType,
) -> CompilerFactIntegerDomain? {
  match self.value_facts(function, site, instruction, expected) {
    Some(facts) =>
      compiler_fact_query_integer_domain_from_facts(facts, expected, self.stats)
    None => None
  }
}

///|
fn CompilerFactQuery::exact_call_target(
  self : CompilerFactQuery,
  function : UInt32,
  call_site : @representation.CodeSite,
  call_instruction : @lib.Instruction,
  target~ : (@representation.ValueSite, @lib.Instruction)? = None,
) -> UInt32? {
  if self.version != 1U {
    self.stats.unsupported_versions += 1
    return None
  }
  if self.policy != CompilerFactUsePolicy::trust_assertions() {
    self.stats.facts_blocked_by_trust += 1
    return None
  }
  if call_site.function != function ||
    !self.site_resolves(call_site, call_instruction) {
    return None
  }
  let call_target = match self.index.call_facts(call_site) {
    Some(facts) => facts.direct_target
    None => None
  }
  let value_target = match target {
    Some((value_site, producer)) =>
      match self.index.value_facts(value_site) {
        Some(_) =>
          match
            compiler_fact_query_result_type(
              producer,
              value_site.result,
              self.env,
            ) {
            Some(expected) =>
              match self.value_facts(function, value_site, producer, expected) {
                Some(facts) =>
                  compiler_fact_exact_target_from_value_facts(facts)
                None => None
              }
            None => {
              self.stats.type_mismatches += 1
              None
            }
          }
        None => None
      }
    None => None
  }
  let resolved = match (call_target, value_target) {
    (Some(left), Some(right)) if left == right => Some(left)
    (Some(_), Some(_)) => {
      self.stats.malformed_or_impossible_facts += 1
      None
    }
    (Some(value), None) | (None, Some(value)) => Some(value)
    _ => None
  }
  match resolved {
    Some(target) if self.env.has_func(@lib.FuncIdx::new(target)) => Some(target)
    Some(_) => {
      self.stats.invalid_referenced_indices += 1
      None
    }
    None => None
  }
}

///|
fn CompilerFactQuery::null_test(
  self : CompilerFactQuery,
  function : UInt32,
  site : @representation.ValueSite,
  instruction : @lib.Instruction,
  expected : @lib.ValType,
) -> CompilerFactTruth {
  match expected {
    @lib.RefTypeValType(_) => ()
    _ => {
      self.stats.type_mismatches += 1
      return FactUnknown
    }
  }
  let facts = match self.value_facts(function, site, instruction, expected) {
    Some(facts) => facts
    None => return FactUnknown
  }
  let from_values = match facts.possible_values {
    Some(@representation.ClosedValueSet([@representation.RefNullValue(_)])) =>
      FactProvenTrue
    Some(@representation.ClosedValueSet([@representation.RefFuncValue(_)])) =>
      FactProvenFalse
    _ => FactUnknown
  }
  let from_reference = match facts.reference {
    Some(reference) =>
      match reference.nullability {
        Some(@representation.NullOnly) => FactProvenTrue
        Some(@representation.NonNull) => FactProvenFalse
        None => FactUnknown
      }
    None => FactUnknown
  }
  match (from_values, from_reference) {
    (FactProvenTrue, FactProvenFalse) | (FactProvenFalse, FactProvenTrue) => {
      self.stats.malformed_or_impossible_facts += 1
      FactUnknown
    }
    (FactProvenTrue, _) | (_, FactProvenTrue) => FactProvenTrue
    (FactProvenFalse, _) | (_, FactProvenFalse) => FactProvenFalse
    _ => FactUnknown
  }
}

///|
fn compiler_fact_relation_equal(
  left : CompilerFactIntegerDomain,
  right : CompilerFactIntegerDomain,
) -> CompilerFactTruth {
  match (left.exact_bits, right.exact_bits) {
    (Some(lhs), Some(rhs)) =>
      if lhs == rhs {
        FactProvenTrue
      } else {
        FactProvenFalse
      }
    (Some(0UL), None) if right.nonzero => FactProvenFalse
    (None, Some(0UL)) if left.nonzero => FactProvenFalse
    _ =>
      match
        (
          left.unsigned_min,
          left.unsigned_max,
          right.unsigned_min,
          right.unsigned_max,
        ) {
        (Some(lmin), Some(lmax), Some(rmin), Some(rmax)) if lmin == lmax &&
          rmin == rmax &&
          lmin == rmin => FactProvenTrue
        (Some(lmin), Some(lmax), Some(rmin), Some(rmax)) if lmax < rmin ||
          rmax < lmin => FactProvenFalse
        _ =>
          match
            (
              left.signed_min,
              left.signed_max,
              right.signed_min,
              right.signed_max,
            ) {
            (Some(lmin), Some(lmax), Some(rmin), Some(rmax)) if lmin == lmax &&
              rmin == rmax &&
              lmin == rmin => FactProvenTrue
            (Some(lmin), Some(lmax), Some(rmin), Some(rmax)) if lmax < rmin ||
              rmax < lmin => FactProvenFalse
            _ => FactUnknown
          }
      }
  }
}

///|
fn compiler_fact_relation_signed_less_than(
  left : CompilerFactIntegerDomain,
  right : CompilerFactIntegerDomain,
) -> CompilerFactTruth {
  match (left.signed_min, left.signed_max, right.signed_min, right.signed_max) {
    (Some(lmin), Some(lmax), Some(rmin), Some(rmax)) =>
      if lmax < rmin {
        FactProvenTrue
      } else if lmin >= rmax {
        FactProvenFalse
      } else {
        FactUnknown
      }
    _ => FactUnknown
  }
}

///|
fn compiler_fact_relation_unsigned_less_than(
  left : CompilerFactIntegerDomain,
  right : CompilerFactIntegerDomain,
) -> CompilerFactTruth {
  match
    (
      left.unsigned_min,
      left.unsigned_max,
      right.unsigned_min,
      right.unsigned_max,
    ) {
    (Some(lmin), Some(lmax), Some(rmin), Some(rmax)) =>
      if lmax < rmin {
        FactProvenTrue
      } else if lmin >= rmax {
        FactProvenFalse
      } else {
        FactUnknown
      }
    _ => FactUnknown
  }
}

///|
fn compiler_fact_truth_not(value : CompilerFactTruth) -> CompilerFactTruth {
  match value {
    FactProvenTrue => FactProvenFalse
    FactProvenFalse => FactProvenTrue
    FactUnknown => FactUnknown
  }
}

///|
fn compiler_fact_relation_signed_less_or_equal(
  left : CompilerFactIntegerDomain,
  right : CompilerFactIntegerDomain,
) -> CompilerFactTruth {
  compiler_fact_truth_not(compiler_fact_relation_signed_less_than(right, left))
}

///|
fn compiler_fact_relation_unsigned_less_or_equal(
  left : CompilerFactIntegerDomain,
  right : CompilerFactIntegerDomain,
) -> CompilerFactTruth {
  compiler_fact_truth_not(
    compiler_fact_relation_unsigned_less_than(right, left),
  )
}

///|
fn compiler_fact_relation_for_instruction(
  instruction : @lib.Instruction,
  left : CompilerFactIntegerDomain,
  right : CompilerFactIntegerDomain,
) -> CompilerFactTruth {
  match instruction {
    @lib.I32Eq | @lib.I64Eq => compiler_fact_relation_equal(left, right)
    @lib.I32Ne | @lib.I64Ne =>
      compiler_fact_truth_not(compiler_fact_relation_equal(left, right))
    @lib.I32LtS | @lib.I64LtS =>
      compiler_fact_relation_signed_less_than(left, right)
    @lib.I32LtU | @lib.I64LtU =>
      compiler_fact_relation_unsigned_less_than(left, right)
    @lib.I32LeS | @lib.I64LeS =>
      compiler_fact_relation_signed_less_or_equal(left, right)
    @lib.I32LeU | @lib.I64LeU =>
      compiler_fact_relation_unsigned_less_or_equal(left, right)
    @lib.I32GtS | @lib.I64GtS =>
      compiler_fact_relation_signed_less_than(right, left)
    @lib.I32GtU | @lib.I64GtU =>
      compiler_fact_relation_unsigned_less_than(right, left)
    @lib.I32GeS | @lib.I64GeS =>
      compiler_fact_relation_signed_less_or_equal(right, left)
    @lib.I32GeU | @lib.I64GeU =>
      compiler_fact_relation_unsigned_less_or_equal(right, left)
    _ => FactUnknown
  }
}