// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/objects/turbofan-types.h"

const kMaxIntPtr: constexpr IntegerLiteral
    generates 'IntegerLiteral(ca_.Is64() ? 0x7FFFFFFFFFFFFFFF : 0x7FFFFFFF)';
const kMinIntPtr: constexpr IntegerLiteral
    generates 'IntegerLiteral(ca_.Is64() ? 0x8000000000000000 : 0x80000000)';

@export
@abstract
class TurbofanType extends HeapObject {
}

// TurbofanBitsetType is 64 bit.
// We use two separate 32 bit bitsets in Torque, due to limitted support
// of 64 bit bitsets.
bitfield struct TurbofanTypeLowBits extends uint32 {
  _unused_padding_field_1: bool: 1 bit;
  other_unsigned31: bool: 1 bit;
  other_unsigned32: bool: 1 bit;
  other_signed32: bool: 1 bit;
  other_number: bool: 1 bit;
  other_string: bool: 1 bit;
  negative31: bool: 1 bit;
  null: bool: 1 bit;
  undefined: bool: 1 bit;
  boolean: bool: 1 bit;
  unsigned30: bool: 1 bit;
  minus_zero: bool: 1 bit;
  naN: bool: 1 bit;
  symbol: bool: 1 bit;
  internalized_string: bool: 1 bit;
  other_callable: bool: 1 bit;
  other_object: bool: 1 bit;
  other_undetectable: bool: 1 bit;
  callable_proxy: bool: 1 bit;
  other_proxy: bool: 1 bit;
  callable_function: bool: 1 bit;
  class_constructor: bool: 1 bit;
  bound_function: bool: 1 bit;
  hole: bool: 1 bit;
  other_internal: bool: 1 bit;
  external_pointer: bool: 1 bit;
  array: bool: 1 bit;
  unsigned_big_int_63: bool: 1 bit;
  other_unsigned_big_int_64: bool: 1 bit;
  negative_big_int_63: bool: 1 bit;
  other_big_int: bool: 1 bit;
  wasm_object: bool: 1 bit;
}

bitfield struct TurbofanTypeHighBits extends uint32 {
  sandboxed_pointer: bool: 1 bit;
}

@export
class TurbofanBitsetType extends TurbofanType {
  bitset_low: TurbofanTypeLowBits;
  bitset_high: TurbofanTypeHighBits;
}

@export
class TurbofanUnionType extends TurbofanType {
  type1: TurbofanType;
  type2: TurbofanType;
}

@export
class TurbofanRangeType extends TurbofanType {
  min: float64;
  max: float64;
}

@export
class TurbofanHeapConstantType extends TurbofanType {
  constant: HeapObject;
}

@export
class TurbofanOtherNumberConstantType extends TurbofanType {
  constant: float64;
}

macro IsMinusZero(x: float64): bool {
  return x == 0 && 1.0 / x < 0;
}

macro TestTurbofanBitsetType(
    value: Object, bitsetLow: TurbofanTypeLowBits,
    bitsetHigh: TurbofanTypeHighBits): bool {
  // Silence unused warnings on builds that don't need {bitsetHigh}.
  const _unused = bitsetHigh;
  typeswitch (value) {
    case (value: Number): {
      const valueF = Convert<float64>(value);
      if (IsInteger(value)) {
        if (IsMinusZero(valueF)) {
          return bitsetLow.minus_zero;
        } else if (valueF < -0x80000000) {
          return bitsetLow.other_number;
        } else if (valueF < -0x40000000) {
          return bitsetLow.other_signed32;
        } else if (valueF < 0) {
          return bitsetLow.negative31;
        } else if (valueF < 0x40000000) {
          return bitsetLow.unsigned30;
        } else if (valueF < 0x80000000) {
          return bitsetLow.other_unsigned31;
        } else if (valueF <= 0xffffffff) {
          return bitsetLow.other_unsigned32;
        } else {
          return bitsetLow.other_number;
        }
      } else if (Float64IsNaN(valueF)) {
        return bitsetLow.naN;
      } else {
        return bitsetLow.other_number;
      }
    }
    case (Null): {
      return bitsetLow.null;
    }
    case (Undefined): {
      return bitsetLow.undefined;
    }
    case (Boolean): {
      return bitsetLow.boolean;
    }
    case (Symbol): {
      return bitsetLow.symbol;
    }
    case (s: String): {
      if (s.IsNotInternalized()) {
        return bitsetLow.other_string;
      } else {
        return bitsetLow.internalized_string;
      }
    }
    case (proxy: JSProxy): {
      return Is<Callable>(proxy) ? bitsetLow.callable_proxy :
                                   bitsetLow.other_proxy;
    }
    case (fun: JSFunction): {
      if (fun.shared_function_info.flags.is_class_constructor) {
        return bitsetLow.class_constructor;
      } else {
        return bitsetLow.callable_function;
      }
    }
    case (JSBoundFunction): {
      return bitsetLow.bound_function;
    }
    case (TheHole): {
      return bitsetLow.hole;
    }
    case (JSArray): {
      return bitsetLow.array;
    }
    case (bi: BigInt): {
      dcheck(!bitsetLow.other_big_int || bitsetLow.other_unsigned_big_int_64);
      dcheck(!bitsetLow.other_big_int || bitsetLow.negative_big_int_63);
      dcheck(
          !bitsetLow.other_unsigned_big_int_64 ||
          bitsetLow.unsigned_big_int_63);
      dcheck(!bitsetLow.negative_big_int_63 || bitsetLow.unsigned_big_int_63);

      // On 32 bit architectures, [Un]signedBigInt64 types are not used, yet.
      if (!Is64()) {
        return bitsetLow.other_big_int;
      }

      const length = bigint::ReadBigIntLength(bi);
      if (length > 1) {
        return bitsetLow.other_big_int;
      } else if (length == 0) {
        return bitsetLow.unsigned_big_int_63;
      }
      dcheck(length == 1);
      const sign = bigint::ReadBigIntSign(bi);
      const digit = bigint::LoadBigIntDigit(bi, 0);
      if (sign == bigint::kPositiveSign) {
        return bitsetLow.other_unsigned_big_int_64 ||
            (digit <= Convert<uintptr>(kMaxIntPtr) &&
             bitsetLow.unsigned_big_int_63);
      } else {
        return bitsetLow.other_big_int ||
            (digit <= Convert<uintptr>(kMinIntPtr) &&
             bitsetLow.negative_big_int_63);
      }
    }
    case (object: JSObject): {
      if (object.map.IsUndetectable()) {
        return bitsetLow.other_undetectable;
      } else if (Is<Callable>(object)) {
        return bitsetLow.other_callable;
      } else {
        return bitsetLow.other_object;
      }
    }
    @if(V8_ENABLE_WEBASSEMBLY)
    case (WasmObject): {
      return bitsetLow.wasm_object;
    }
    case (Object): {
      return false;
    }
  }
}

builtin TestTurbofanType(implicit context: Context)(
    value: Object, expectedType: TurbofanType): Boolean {
  typeswitch (expectedType) {
    case (t: TurbofanBitsetType): {
      return Convert<Boolean>(
          TestTurbofanBitsetType(value, t.bitset_low, t.bitset_high));
    }
    case (t: TurbofanUnionType): {
      return Convert<Boolean>(
          TestTurbofanType(value, t.type1) == True ||
          TestTurbofanType(value, t.type2) == True);
    }
    case (t: TurbofanRangeType): {
      const value = Cast<Number>(value) otherwise return False;
      if (!IsIntegerOrSomeInfinity(value)) return False;
      const valueF = Convert<float64>(value);
      return Convert<Boolean>(
          !IsMinusZero(valueF) && t.min <= valueF && valueF <= t.max);
    }
    case (t: TurbofanHeapConstantType): {
      return Convert<Boolean>(TaggedEqual(value, t.constant));
    }
    case (t: TurbofanOtherNumberConstantType): {
      const value =
          Convert<float64>(Cast<Number>(value) otherwise return False);
      return Convert<Boolean>(value == t.constant);
    }
    case (TurbofanType): {
      unreachable;
    }
  }
}

builtin CheckTurbofanType(implicit context: Context)(
    value: Object, expectedType: TurbofanType, nodeId: Smi): Undefined {
  if (TestTurbofanType(value, expectedType) == True) {
    return Undefined;
  }

  Print('Type assertion failed! (value/expectedType/nodeId)');
  Print(value);
  Print(expectedType);
  Print(nodeId);
  unreachable;
}