// Copyright 2019 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/ic/binary-op-assembler.h'

extern enum Operation extends uint31 {
  // Binary operations.
  kAdd,
  kSubtract,
  kMultiply,
  kDivide,
  kModulus,
  kExponentiate,
  kBitwiseAnd,
  kBitwiseOr,
  kBitwiseXor,
  kShiftLeft,
  kShiftRight,
  kShiftRightLogical,
  // Unary operations.
  kBitwiseNot,
  kNegate,
  kIncrement,
  kDecrement,
  // Compare operations.
  kEqual,
  kStrictEqual,
  kLessThan,
  kLessThanOrEqual,
  kGreaterThan,
  kGreaterThanOrEqual
}

namespace runtime {
extern transitioning runtime
DoubleToStringWithRadix(implicit context: Context)(Number, Number): String;

extern transitioning runtime StringParseFloat(implicit context: Context)(
    String): Number;
extern transitioning runtime StringParseInt(implicit context: Context)(
    JSAny, JSAny): Number;

extern runtime BigIntUnaryOp(Context, BigInt, SmiTagged<Operation>): BigInt;
extern runtime BigIntBinaryOp(
    Context, Numeric, Numeric, SmiTagged<Operation>): BigInt;
}  // namespace runtime

namespace number {
extern macro NaNStringConstant(): String;
extern macro ZeroStringConstant(): String;
extern macro InfinityStringConstant(): String;
extern macro MinusInfinityStringConstant(): String;

const kAsciiZero: constexpr int32 = 48;        // '0' (ascii)
const kAsciiLowerCaseA: constexpr int32 = 97;  // 'a' (ascii)

transitioning macro ThisNumberValue(implicit context: Context)(
    receiver: JSAny, method: constexpr string): Number {
  return UnsafeCast<Number>(
      ToThisValue(receiver, PrimitiveType::kNumber, method));
}

macro ToCharCode(input: int32): char8 {
  assert(0 <= input && input < 36);
  return input < 10 ? %RawDownCast<char8>(input + kAsciiZero) :
                      %RawDownCast<char8>(input - 10 + kAsciiLowerCaseA);
}

// https://tc39.github.io/ecma262/#sec-number.prototype.tostring
transitioning javascript builtin NumberPrototypeToString(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): String {
  // 1. Let x be ? thisNumberValue(this value).
  const x = ThisNumberValue(receiver, 'Number.prototype.toString');

  // 2. If radix is not present, let radixNumber be 10.
  // 3. Else if radix is undefined, let radixNumber be 10.
  // 4. Else, let radixNumber be ? ToInteger(radix).
  const radix: JSAny = arguments[0];
  const radixNumber: Number = radix == Undefined ? 10 : ToInteger_Inline(radix);

  // 5. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception.
  if (radixNumber < 2 || radixNumber > 36) {
    ThrowRangeError(MessageTemplate::kToRadixFormatRange);
  }

  // 6. If radixNumber = 10, return ! ToString(x).
  if (radixNumber == 10) {
    return NumberToString(x);
  }

  // 7. Return the String representation of this Number
  //    value using the radix specified by radixNumber.

  if (TaggedIsSmi(x)) {
    const isNegative: bool = x < 0;
    let n: int32 = Convert<int32>(UnsafeCast<Smi>(x));
    if (!isNegative) {
      // Fast case where the result is a one character string.
      if (x < radixNumber) {
        return StringFromSingleCharCode(ToCharCode(n));
      }
    } else {
      assert(isNegative);
      if (n == kMinInt32) {
        return runtime::DoubleToStringWithRadix(x, radixNumber);
      }
      n = 0 - n;
    }

    const radix: int32 = Convert<int32>(radixNumber);
    // Calculate length and pre-allocate the result string.
    let temp: int32 = n;
    let length: int32 = isNegative ? 1 : 0;
    while (temp > 0) {
      temp = temp / radix;
      length = length + 1;
    }
    assert(length > 0);
    const strSeq = UnsafeCast<SeqOneByteString>(
        AllocateSeqOneByteString(Unsigned(length)));
    let cursor: intptr = Convert<intptr>(length) - 1;
    while (n > 0) {
      const digit: int32 = n % radix;
      n = n / radix;
      strSeq.chars[cursor] = ToCharCode(digit);
      cursor = cursor - 1;
    }
    if (isNegative) {
      assert(cursor == 0);
      // Insert '-' to result.
      strSeq.chars[0] = 45;
    } else {
      assert(cursor == -1);
    }
    return strSeq;
  }

  if (x == -0) {
    return ZeroStringConstant();
  } else if (::NumberIsNaN(x)) {
    return NaNStringConstant();
  } else if (x == V8_INFINITY) {
    return InfinityStringConstant();
  } else if (x == MINUS_V8_INFINITY) {
    return MinusInfinityStringConstant();
  }

  return runtime::DoubleToStringWithRadix(x, radixNumber);
}

// ES6 #sec-number.isfinite
javascript builtin NumberIsFinite(
    js-implicit context: NativeContext,
    receiver: JSAny)(value: JSAny): Boolean {
  typeswitch (value) {
    case (Smi): {
      return True;
    }
    case (h: HeapNumber): {
      const number: float64 = Convert<float64>(h);
      const infiniteOrNaN: bool = Float64IsNaN(number - number);
      return Convert<Boolean>(!infiniteOrNaN);
    }
    case (JSAnyNotNumber): {
      return False;
    }
  }
}

// ES6 #sec-number.isinteger
javascript builtin NumberIsInteger(js-implicit context: NativeContext)(
    value: JSAny): Boolean {
  return SelectBooleanConstant(IsInteger(value));
}

// ES6 #sec-number.isnan
javascript builtin NumberIsNaN(js-implicit context: NativeContext)(
    value: JSAny): Boolean {
  typeswitch (value) {
    case (Smi): {
      return False;
    }
    case (h: HeapNumber): {
      const number: float64 = Convert<float64>(h);
      return Convert<Boolean>(Float64IsNaN(number));
    }
    case (JSAnyNotNumber): {
      return False;
    }
  }
}

// ES6 #sec-number.issafeinteger
javascript builtin NumberIsSafeInteger(js-implicit context: NativeContext)(
    value: JSAny): Boolean {
  return SelectBooleanConstant(IsSafeInteger(value));
}

// ES6 #sec-number.prototype.valueof
transitioning javascript builtin NumberPrototypeValueOf(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  return ToThisValue(
      receiver, PrimitiveType::kNumber, 'Number.prototype.valueOf');
}

// ES6 #sec-number.parsefloat
transitioning javascript builtin NumberParseFloat(
    js-implicit context: NativeContext)(value: JSAny): Number {
  try {
    typeswitch (value) {
      case (s: Smi): {
        return s;
      }
      case (h: HeapNumber): {
        // The input is already a Number. Take care of -0.
        // The sense of comparison is important for the NaN case.
        return (Convert<float64>(h) == 0) ? SmiConstant(0) : h;
      }
      case (s: String): {
        goto String(s);
      }
      case (HeapObject): {
        goto String(string::ToString(context, value));
      }
    }
  } label String(s: String) {
    // Check if the string is a cached array index.
    const hash: NameHash = s.hash_field;
    if (!hash.is_not_integer_index_mask &&
        hash.array_index_length < kMaxCachedArrayIndexLength) {
      const arrayIndex: uint32 = hash.array_index_value;
      return SmiFromUint32(arrayIndex);
    }
    // Fall back to the runtime to convert string to a number.
    return runtime::StringParseFloat(s);
  }
}

extern macro TruncateFloat64ToWord32(float64): uint32;

transitioning builtin ParseInt(implicit context: Context)(
    input: JSAny, radix: JSAny): Number {
  try {
    // Check if radix should be 10 (i.e. undefined, 0 or 10).
    if (radix != Undefined && !TaggedEqual(radix, SmiConstant(10)) &&
        !TaggedEqual(radix, SmiConstant(0))) {
      goto CallRuntime;
    }

    typeswitch (input) {
      case (s: Smi): {
        return s;
      }
      case (h: HeapNumber): {
        // Check if the input value is in Signed32 range.
        const asFloat64: float64 = Convert<float64>(h);
        const asInt32: int32 = Signed(TruncateFloat64ToWord32(asFloat64));
        // The sense of comparison is important for the NaN case.
        if (asFloat64 == ChangeInt32ToFloat64(asInt32)) goto Int32(asInt32);

        // Check if the absolute value of input is in the [1,1<<31[ range. Call
        // the runtime for the range [0,1[ because the result could be -0.
        const kMaxAbsValue: float64 = 2147483648.0;
        const absInput: float64 = math::Float64Abs(asFloat64);
        if (absInput < kMaxAbsValue && absInput >= 1) goto Int32(asInt32);
        goto CallRuntime;
      }
      case (s: String): {
        goto String(s);
      }
      case (HeapObject): {
        goto CallRuntime;
      }
    }
  } label Int32(i: int32) {
    return ChangeInt32ToTagged(i);
  } label String(s: String) {
    // Check if the string is a cached array index.
    const hash: NameHash = s.hash_field;
    if (!hash.is_not_integer_index_mask &&
        hash.array_index_length < kMaxCachedArrayIndexLength) {
      const arrayIndex: uint32 = hash.array_index_value;
      return SmiFromUint32(arrayIndex);
    }
    // Fall back to the runtime.
    goto CallRuntime;
  } label CallRuntime {
    tail runtime::StringParseInt(input, radix);
  }
}

// ES6 #sec-number.parseint
transitioning javascript builtin NumberParseInt(
    js-implicit context: NativeContext)(value: JSAny, radix: JSAny): Number {
  return ParseInt(value, radix);
}

extern builtin NonNumberToNumeric(implicit context: Context)(JSAny): Numeric;
extern builtin BitwiseXor(implicit context: Context)(Number, Number): Number;
extern builtin Subtract(implicit context: Context)(Number, Number): Number;
extern builtin Add(implicit context: Context)(Number, Number): Number;
extern builtin StringAddConvertLeft(implicit context: Context)(
    JSAny, String): JSAny;
extern builtin StringAddConvertRight(implicit context: Context)(
    String, JSAny): JSAny;

extern macro BitwiseOp(int32, int32, constexpr Operation): Number;
extern macro RelationalComparison(
    constexpr Operation, JSAny, JSAny, Context): Boolean;

// TODO(bbudge) Use a simpler macro structure that doesn't loop when converting
// non-numbers, if such a code sequence doesn't make the builtin bigger.

transitioning macro ToNumericOrPrimitive(implicit context: Context)(
    value: JSAny): JSAny {
  typeswitch (value) {
    case (v: JSReceiver): {
      return NonPrimitiveToPrimitive_Default(context, v);
    }
    case (v: JSPrimitive): {
      return NonNumberToNumeric(v);
    }
  }
}

transitioning builtin Add(implicit context: Context)(
    leftArg: JSAny, rightArg: JSAny): JSAny {
  let left: JSAny = leftArg;
  let right: JSAny = rightArg;
  try {
    while (true) {
      typeswitch (left) {
        case (left: Smi): {
          typeswitch (right) {
            case (right: Smi): {
              return math::TrySmiAdd(left, right) otherwise goto Float64s(
                  SmiToFloat64(left), SmiToFloat64(right));
            }
            case (right: HeapNumber): {
              goto Float64s(SmiToFloat64(left), Convert<float64>(right));
            }
            case (right: BigInt): {
              goto Numerics(left, right);
            }
            case (right: String): {
              goto StringAddConvertLeft(left, right);
            }
            case (HeapObject): {
              right = ToNumericOrPrimitive(right);
              continue;
            }
          }
        }
        case (left: HeapNumber): {
          typeswitch (right) {
            case (right: Smi): {
              goto Float64s(Convert<float64>(left), SmiToFloat64(right));
            }
            case (right: HeapNumber): {
              goto Float64s(Convert<float64>(left), Convert<float64>(right));
            }
            case (right: BigInt): {
              goto Numerics(left, right);
            }
            case (right: String): {
              goto StringAddConvertLeft(left, right);
            }
            case (HeapObject): {
              right = ToNumericOrPrimitive(right);
              continue;
            }
          }
        }
        case (left: BigInt): {
          typeswitch (right) {
            case (right: Numeric): {
              goto Numerics(left, right);
            }
            case (right: String): {
              goto StringAddConvertLeft(left, right);
            }
            case (HeapObject): {
              right = ToNumericOrPrimitive(right);
              continue;
            }
          }
        }
        case (left: String): {
          goto StringAddConvertRight(left, right);
        }
        case (leftReceiver: JSReceiver): {
          left = ToPrimitiveDefault(leftReceiver);
        }
        case (HeapObject): {
          // left: HeapObject
          typeswitch (right) {
            case (right: String): {
              goto StringAddConvertLeft(left, right);
            }
            case (rightReceiver: JSReceiver): {
              // left is JSPrimitive and right is JSReceiver, convert right
              // with priority.
              right = ToPrimitiveDefault(rightReceiver);
              continue;
            }
            case (JSPrimitive): {
              // Neither left or right is JSReceiver, convert left.
              left = NonNumberToNumeric(left);
              continue;
            }
          }
        }
      }
    }
  } label StringAddConvertLeft(left: JSAny, right: String) {
    tail StringAddConvertLeft(left, right);
  } label StringAddConvertRight(left: String, right: JSAny) {
    tail StringAddConvertRight(left, right);
  } label Numerics(left: Numeric, right: Numeric) {
    tail bigint::BigIntAdd(left, right);
  } label Float64s(left: float64, right: float64) {
    return AllocateHeapNumberWithValue(left + right);
  }
  unreachable;
}

// Unary type switch on Number | BigInt.
macro UnaryOp1(implicit context: Context)(value: JSAny): never labels
Number(Number), BigInt(BigInt) {
  let x: JSAny = value;
  while (true) {
    typeswitch (x) {
      case (n: Number): {
        goto Number(n);
      }
      case (b: BigInt): {
        goto BigInt(b);
      }
      case (JSAnyNotNumeric): {
        x = NonNumberToNumeric(x);
      }
    }
  }
  unreachable;
}

// Unary type switch on Smi | HeapNumber | BigInt.
macro UnaryOp2(implicit context: Context)(value: JSAny): never labels
Smi(Smi), HeapNumber(HeapNumber), BigInt(BigInt) {
  let x: JSAny = value;
  while (true) {
    typeswitch (x) {
      case (s: Smi): {
        goto Smi(s);
      }
      case (h: HeapNumber): {
        goto HeapNumber(h);
      }
      case (b: BigInt): {
        goto BigInt(b);
      }
      case (JSAnyNotNumeric): {
        x = NonNumberToNumeric(x);
      }
    }
  }
  unreachable;
}

// Binary type switch on Number | BigInt.
macro BinaryOp1(implicit context: Context)(
    leftVal: JSAny, rightVal: JSAny): never labels
Number(Number, Number), AtLeastOneBigInt(Numeric, Numeric) {
  let left: JSAny = leftVal;
  let right: JSAny = rightVal;
  while (true) {
    try {
      typeswitch (left) {
        case (left: Number): {
          typeswitch (right) {
            case (right: Number): {
              goto Number(left, right);
            }
            case (right: BigInt): {
              goto AtLeastOneBigInt(left, right);
            }
            case (JSAnyNotNumeric): {
              goto RightNotNumeric;
            }
          }
        }
        case (left: BigInt): {
          typeswitch (right) {
            case (right: Numeric): {
              goto AtLeastOneBigInt(left, right);
            }
            case (JSAnyNotNumeric): {
              goto RightNotNumeric;
            }
          }
        }
        case (JSAnyNotNumeric): {
          left = NonNumberToNumeric(left);
        }
      }
    } label RightNotNumeric {
      right = NonNumberToNumeric(right);
    }
  }
  unreachable;
}

// Binary type switch on Smi | HeapNumber | BigInt.
macro BinaryOp2(implicit context: Context)(leftVal: JSAny, rightVal: JSAny):
    never labels Smis(Smi, Smi), Float64s(float64, float64),
    AtLeastOneBigInt(Numeric, Numeric) {
  let left: JSAny = leftVal;
  let right: JSAny = rightVal;
  while (true) {
    try {
      typeswitch (left) {
        case (left: Smi): {
          typeswitch (right) {
            case (right: Smi): {
              goto Smis(left, right);
            }
            case (right: HeapNumber): {
              goto Float64s(SmiToFloat64(left), Convert<float64>(right));
            }
            case (right: BigInt): {
              goto AtLeastOneBigInt(left, right);
            }
            case (JSAnyNotNumeric): {
              goto RightNotNumeric;
            }
          }
        }
        case (left: HeapNumber): {
          typeswitch (right) {
            case (right: Smi): {
              goto Float64s(Convert<float64>(left), SmiToFloat64(right));
            }
            case (right: HeapNumber): {
              goto Float64s(Convert<float64>(left), Convert<float64>(right));
            }
            case (right: BigInt): {
              goto AtLeastOneBigInt(left, right);
            }
            case (JSAnyNotNumeric): {
              goto RightNotNumeric;
            }
          }
        }
        case (left: BigInt): {
          typeswitch (right) {
            case (right: Numeric): {
              goto AtLeastOneBigInt(left, right);
            }
            case (JSAnyNotNumeric): {
              goto RightNotNumeric;
            }
          }
        }
        case (JSAnyNotNumeric): {
          left = NonNumberToNumeric(left);
        }
      }
    } label RightNotNumeric {
      right = NonNumberToNumeric(right);
    }
  }
  unreachable;
}

builtin Subtract(implicit context: Context)(
    left: JSAny, right: JSAny): Numeric {
  try {
    BinaryOp2(left, right) otherwise Smis, Float64s, AtLeastOneBigInt;
  } label Smis(left: Smi, right: Smi) {
    try {
      return math::TrySmiSub(left, right) otherwise Overflow;
    } label Overflow {
      goto Float64s(SmiToFloat64(left), SmiToFloat64(right));
    }
  } label Float64s(left: float64, right: float64) {
    return AllocateHeapNumberWithValue(left - right);
  } label AtLeastOneBigInt(left: Numeric, right: Numeric) {
    tail bigint::BigIntSubtract(left, right);
  }
}

builtin Multiply(implicit context: Context)(
    left: JSAny, right: JSAny): Numeric {
  try {
    BinaryOp2(left, right) otherwise Smis, Float64s, AtLeastOneBigInt;
  } label Smis(left: Smi, right: Smi) {
    // The result is not necessarily a smi, in case of overflow.
    return SmiMul(left, right);
  } label Float64s(left: float64, right: float64) {
    return AllocateHeapNumberWithValue(left * right);
  } label AtLeastOneBigInt(left: Numeric, right: Numeric) {
    tail runtime::BigIntBinaryOp(
        context, left, right, SmiTag<Operation>(Operation::kMultiply));
  }
}

const kSmiValueSize: constexpr int32 generates 'kSmiValueSize';
const kMinInt32: constexpr int32 generates 'kMinInt';
const kMinInt31: constexpr int32 generates 'kMinInt31';
const kMinimumDividend: int32 = (kSmiValueSize == 32) ? kMinInt32 : kMinInt31;

builtin Divide(implicit context: Context)(left: JSAny, right: JSAny): Numeric {
  try {
    BinaryOp2(left, right) otherwise Smis, Float64s, AtLeastOneBigInt;
  } label Smis(left: Smi, right: Smi) {
    // TODO(jkummerow): Consider just always doing a double division.
    // Bail out if {divisor} is zero.
    if (right == 0) goto SmiBailout(left, right);

    // Bail out if dividend is zero and divisor is negative.
    if (left == 0 && right < 0) goto SmiBailout(left, right);

    const dividend: int32 = SmiToInt32(left);
    const divisor: int32 = SmiToInt32(right);

    // Bail out if dividend is kMinInt31 (or kMinInt32 if Smis are 32 bits)
    // and divisor is -1.
    if (divisor == -1 && dividend == kMinimumDividend) {
      goto SmiBailout(left, right);
    }
    // TODO(epertoso): consider adding a machine instruction that returns
    // both the result and the remainder.
    const result: int32 = dividend / divisor;
    const truncated: int32 = result * divisor;
    if (dividend != truncated) goto SmiBailout(left, right);
    return SmiFromInt32(result);
  } label SmiBailout(left: Smi, right: Smi) {
    goto Float64s(SmiToFloat64(left), SmiToFloat64(right));
  } label Float64s(left: float64, right: float64) {
    return AllocateHeapNumberWithValue(left / right);
  } label AtLeastOneBigInt(left: Numeric, right: Numeric) {
    tail runtime::BigIntBinaryOp(
        context, left, right, SmiTag<Operation>(Operation::kDivide));
  }
}

builtin Modulus(implicit context: Context)(left: JSAny, right: JSAny): Numeric {
  try {
    BinaryOp2(left, right) otherwise Smis, Float64s, AtLeastOneBigInt;
  } label Smis(left: Smi, right: Smi) {
    return SmiMod(left, right);
  } label Float64s(left: float64, right: float64) {
    return AllocateHeapNumberWithValue(left % right);
  } label AtLeastOneBigInt(left: Numeric, right: Numeric) {
    tail runtime::BigIntBinaryOp(
        context, left, right, SmiTag<Operation>(Operation::kModulus));
  }
}

builtin Exponentiate(implicit context: Context)(
    left: JSAny, right: JSAny): Numeric {
  try {
    BinaryOp1(left, right) otherwise Numbers, AtLeastOneBigInt;
  } label Numbers(left: Number, right: Number) {
    return math::MathPowImpl(left, right);
  } label AtLeastOneBigInt(left: Numeric, right: Numeric) {
    tail runtime::BigIntBinaryOp(
        context, left, right, SmiTag<Operation>(Operation::kExponentiate));
  }
}

builtin Negate(implicit context: Context)(value: JSAny): Numeric {
  try {
    UnaryOp2(value) otherwise Smi, HeapNumber, BigInt;
  } label Smi(s: Smi) {
    return SmiMul(s, -1);
  } label HeapNumber(h: HeapNumber) {
    return AllocateHeapNumberWithValue(Convert<float64>(h) * -1);
  } label BigInt(b: BigInt) {
    tail runtime::BigIntUnaryOp(
        context, b, SmiTag<Operation>(Operation::kNegate));
  }
}

builtin BitwiseNot(implicit context: Context)(value: JSAny): Numeric {
  try {
    UnaryOp1(value) otherwise Number, BigInt;
  } label Number(n: Number) {
    tail BitwiseXor(n, -1);
  } label BigInt(b: BigInt) {
    return runtime::BigIntUnaryOp(
        context, b, SmiTag<Operation>(Operation::kBitwiseNot));
  }
}

builtin Decrement(implicit context: Context)(value: JSAny): Numeric {
  try {
    UnaryOp1(value) otherwise Number, BigInt;
  } label Number(n: Number) {
    tail Subtract(n, 1);
  } label BigInt(b: BigInt) {
    return runtime::BigIntUnaryOp(
        context, b, SmiTag<Operation>(Operation::kDecrement));
  }
}

builtin Increment(implicit context: Context)(value: JSAny): Numeric {
  try {
    UnaryOp1(value) otherwise Number, BigInt;
  } label Number(n: Number) {
    tail Add(n, 1);
  } label BigInt(b: BigInt) {
    return runtime::BigIntUnaryOp(
        context, b, SmiTag<Operation>(Operation::kIncrement));
  }
}

// Bitwise binary operations.

extern macro BinaryOpAssembler::Generate_BitwiseBinaryOp(
    constexpr Operation, JSAny, JSAny, Context): Object;

builtin ShiftLeft(implicit context: Context)(
    left: JSAny, right: JSAny): Object {
  return Generate_BitwiseBinaryOp(Operation::kShiftLeft, left, right, context);
}

builtin ShiftRight(implicit context: Context)(
    left: JSAny, right: JSAny): Object {
  return Generate_BitwiseBinaryOp(Operation::kShiftRight, left, right, context);
}

builtin ShiftRightLogical(implicit context: Context)(
    left: JSAny, right: JSAny): Object {
  return Generate_BitwiseBinaryOp(
      Operation::kShiftRightLogical, left, right, context);
}

builtin BitwiseAnd(implicit context: Context)(
    left: JSAny, right: JSAny): Object {
  return Generate_BitwiseBinaryOp(Operation::kBitwiseAnd, left, right, context);
}

builtin BitwiseOr(implicit context: Context)(
    left: JSAny, right: JSAny): Object {
  return Generate_BitwiseBinaryOp(Operation::kBitwiseOr, left, right, context);
}

builtin BitwiseXor(implicit context: Context)(
    left: JSAny, right: JSAny): Object {
  return Generate_BitwiseBinaryOp(Operation::kBitwiseXor, left, right, context);
}

// Relational builtins.

builtin LessThan(implicit context: Context)(left: JSAny, right: JSAny): Object {
  return RelationalComparison(Operation::kLessThan, left, right, context);
}

builtin LessThanOrEqual(implicit context: Context)(
    left: JSAny, right: JSAny): Object {
  return RelationalComparison(
      Operation::kLessThanOrEqual, left, right, context);
}

builtin GreaterThan(implicit context: Context)(
    left: JSAny, right: JSAny): Object {
  return RelationalComparison(Operation::kGreaterThan, left, right, context);
}

builtin GreaterThanOrEqual(implicit context: Context)(
    left: JSAny, right: JSAny): Object {
  return RelationalComparison(
      Operation::kGreaterThanOrEqual, left, right, context);
}

builtin Equal(implicit context: Context)(left: JSAny, right: JSAny): Object {
  return Equal(left, right, context);
}

builtin StrictEqual(implicit context: Context)(
    left: JSAny, right: JSAny): Object {
  return ::StrictEqual(left, right);
}

}  // namespace number