// 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/builtins/builtins-bigint-gen.h'

// TODO(nicohartmann): Discuss whether types used by multiple builtins should be
// in global namespace
@noVerifier
extern class BigIntBase extends HeapObject generates 'TNode<BigInt>' {
}

type BigInt extends BigIntBase;

@noVerifier
@hasSameInstanceTypeAsParent
extern class MutableBigInt extends BigIntBase generates 'TNode<BigInt>' {
}

Convert<BigInt, MutableBigInt>(i: MutableBigInt): BigInt {
  assert(bigint::IsCanonicalized(i));
  return %RawDownCast<BigInt>(Convert<BigIntBase>(i));
}

namespace bigint {

  const kPositiveSign: uint32 = 0;
  const kNegativeSign: uint32 = 1;

  extern macro BigIntBuiltinsAssembler::CppAbsoluteAddAndCanonicalize(
      MutableBigInt, BigIntBase, BigIntBase): void;
  extern macro BigIntBuiltinsAssembler::CppAbsoluteSubAndCanonicalize(
      MutableBigInt, BigIntBase, BigIntBase): void;
  extern macro BigIntBuiltinsAssembler::CppAbsoluteCompare(
      BigIntBase, BigIntBase): int32;

  extern macro BigIntBuiltinsAssembler::ReadBigIntSign(BigIntBase): uint32;
  extern macro BigIntBuiltinsAssembler::ReadBigIntLength(BigIntBase): intptr;
  extern macro BigIntBuiltinsAssembler::WriteBigIntSignAndLength(
      MutableBigInt, uint32, intptr): void;

  extern macro CodeStubAssembler::AllocateBigInt(intptr): MutableBigInt;
  extern macro CodeStubAssembler::StoreBigIntDigit(
      MutableBigInt, intptr, uintptr): void;
  extern macro CodeStubAssembler::LoadBigIntDigit(BigIntBase, intptr): uintptr;

  @export  // Silence unused warning.
           // TODO(szuend): Remove @export once macros that are only used in
           //               asserts are no longer detected as unused.
  macro IsCanonicalized(bigint: BigIntBase): bool {
    const length = ReadBigIntLength(bigint);

    if (length == 0) {
      return ReadBigIntSign(bigint) == kPositiveSign;
    }

    return LoadBigIntDigit(bigint, length - 1) != 0;
  }

  macro InvertSign(sign: uint32): uint32 {
    return sign == kPositiveSign ? kNegativeSign : kPositiveSign;
  }

  macro AllocateEmptyBigIntNoThrow(implicit context: Context)(
      sign: uint32, length: intptr): MutableBigInt labels BigIntTooBig {
    if (length > kBigIntMaxLength) {
      goto BigIntTooBig;
    }
    const result: MutableBigInt = AllocateBigInt(length);

    WriteBigIntSignAndLength(result, sign, length);
    return result;
  }

  macro AllocateEmptyBigInt(implicit context: Context)(
      sign: uint32, length: intptr): MutableBigInt {
    try {
      return AllocateEmptyBigIntNoThrow(sign, length) otherwise BigIntTooBig;
    }
    label BigIntTooBig {
      ThrowRangeError(kBigIntTooBig);
    }
  }

  macro MutableBigIntAbsoluteCompare(x: BigIntBase, y: BigIntBase): int32 {
    return CppAbsoluteCompare(x, y);
  }

  macro MutableBigIntAbsoluteSub(implicit context: Context)(
      x: BigInt, y: BigInt, resultSign: uint32): BigInt {
    const xlength = ReadBigIntLength(x);
    const ylength = ReadBigIntLength(y);
    const xsign = ReadBigIntSign(x);

    assert(MutableBigIntAbsoluteCompare(x, y) >= 0);
    if (xlength == 0) {
      assert(ylength == 0);
      return x;
    }

    if (ylength == 0) {
      return resultSign == xsign ? x : BigIntUnaryMinus(x);
    }

    const result = AllocateEmptyBigInt(resultSign, xlength);
    CppAbsoluteSubAndCanonicalize(result, x, y);
    return Convert<BigInt>(result);
  }

  macro MutableBigIntAbsoluteAdd(implicit context: Context)(
      xBigint: BigInt, yBigint: BigInt,
      resultSign: uint32): BigInt labels BigIntTooBig {
    let xlength = ReadBigIntLength(xBigint);
    let ylength = ReadBigIntLength(yBigint);

    let x = xBigint;
    let y = yBigint;
    if (xlength < ylength) {
      // Swap x and y so that x is longer.
      x = yBigint;
      y = xBigint;
      const tempLength = xlength;
      xlength = ylength;
      ylength = tempLength;
    }

    // case: 0n + 0n
    if (xlength == 0) {
      assert(ylength == 0);
      return x;
    }

    // case: x + 0n
    if (ylength == 0) {
      return resultSign == ReadBigIntSign(x) ? x : BigIntUnaryMinus(x);
    }

    // case: x + y
    const result = AllocateEmptyBigIntNoThrow(resultSign, xlength + 1)
        otherwise BigIntTooBig;
    CppAbsoluteAddAndCanonicalize(result, x, y);
    return Convert<BigInt>(result);
  }

  macro BigIntAddImpl(implicit context: Context)(x: BigInt, y: BigInt): BigInt
      labels BigIntTooBig {
    const xsign = ReadBigIntSign(x);
    const ysign = ReadBigIntSign(y);
    if (xsign == ysign) {
      // x + y == x + y
      // -x + -y == -(x + y)
      return MutableBigIntAbsoluteAdd(x, y, xsign) otherwise BigIntTooBig;
    }

    // x + -y == x - y == -(y - x)
    // -x + y == y - x == -(x - y)
    if (MutableBigIntAbsoluteCompare(x, y) >= 0) {
      return MutableBigIntAbsoluteSub(x, y, xsign);
    }
    return MutableBigIntAbsoluteSub(y, x, InvertSign(xsign));
  }

  builtin BigIntAddNoThrow(implicit context: Context)(x: BigInt, y: BigInt):
      Numeric {
    try {
      return BigIntAddImpl(x, y) otherwise BigIntTooBig;
    }
    label BigIntTooBig {
      // Smi sentinal is used to signal BigIntTooBig exception.
      return Convert<Smi>(0);
    }
  }

  builtin BigIntAdd(implicit context: Context)(xNum: Numeric, yNum: Numeric):
      BigInt {
    try {
      const x = Cast<BigInt>(xNum) otherwise MixedTypes;
      const y = Cast<BigInt>(yNum) otherwise MixedTypes;

      return BigIntAddImpl(x, y) otherwise BigIntTooBig;
    }
    label MixedTypes {
      ThrowTypeError(kBigIntMixedTypes);
    }
    label BigIntTooBig {
      ThrowRangeError(kBigIntTooBig);
    }
  }

  builtin BigIntUnaryMinus(implicit context: Context)(bigint: BigInt): BigInt {
    const length = ReadBigIntLength(bigint);

    // There is no -0n.
    if (length == 0) {
      return bigint;
    }

    const result =
        AllocateEmptyBigInt(InvertSign(ReadBigIntSign(bigint)), length);
    for (let i: intptr = 0; i < length; ++i) {
      StoreBigIntDigit(result, i, LoadBigIntDigit(bigint, i));
    }
    return Convert<BigInt>(result);
  }

}  // namespace bigint