// 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'

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;

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(MessageTemplate::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);

  dcheck(MutableBigIntAbsoluteCompare(x, y) >= 0);
  if (xlength == 0) {
    dcheck(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) {
    dcheck(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(MessageTemplate::kBigIntMixedTypes);
  } label BigIntTooBig {
    ThrowRangeError(MessageTemplate::kBigIntTooBig);
  }
}

macro BigIntSubtractImpl(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 == -(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 BigIntSubtractNoThrow(implicit context: Context)(
    x: BigInt, y: BigInt): Numeric {
  try {
    return BigIntSubtractImpl(x, y) otherwise BigIntTooBig;
  } label BigIntTooBig {
    // Smi sentinal is used to signal BigIntTooBig exception.
    return Convert<Smi>(0);
  }
}

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

    return BigIntSubtractImpl(x, y) otherwise BigIntTooBig;
  } label MixedTypes {
    ThrowTypeError(MessageTemplate::kBigIntMixedTypes);
  } label BigIntTooBig {
    ThrowRangeError(MessageTemplate::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