/* Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ecma-bigint.h"
#include "ecma-big-uint.h"
#include "ecma-exceptions.h"
#include "ecma-helpers.h"
#include "ecma-objects.h"
#include "lit-char-helpers.h"

#if JERRY_BUILTIN_BIGINT

/**
 * Raise a not enough memory error
 *
 * @return ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
static ecma_value_t
ecma_bigint_raise_memory_error (void)
{
  return ecma_raise_range_error (ECMA_ERR_MSG ("Cannot allocate memory for a BigInt value"));
} /* ecma_bigint_raise_memory_error */

/**
 * Create a single digit long BigInt value
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
static ecma_value_t
ecma_bigint_create_from_digit (ecma_bigint_digit_t digit, /* single digit */
                               bool sign) /* set ECMA_BIGINT_SIGN if true */
{
  JERRY_ASSERT (digit != 0);

  ecma_extended_primitive_t *result_value_p = ecma_bigint_create (sizeof (ecma_bigint_digit_t));

  if (JERRY_UNLIKELY (result_value_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  if (sign)
  {
    result_value_p->u.bigint_sign_and_size |= ECMA_BIGINT_SIGN;
  }

  *ECMA_BIGINT_GET_DIGITS (result_value_p, 0) = digit;
  return ecma_make_extended_primitive_value (result_value_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_create_from_digit */

/**
 * Parse a string and create a BigInt value
 *
 * @return ecma BigInt value or a special value allowed by the option flags
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_parse_string (const lit_utf8_byte_t *string_p, /**< string represenation of the BigInt */
                          lit_utf8_size_t size, /**< string size */
                          uint32_t options) /**< ecma_bigint_parse_string_options_t option bits */
{
  ecma_bigint_digit_t radix = 10;
  uint32_t sign = (options & ECMA_BIGINT_PARSE_SET_NEGATIVE) ? ECMA_BIGINT_SIGN : 0;
  bool allow_underscore = options & ECMA_BIGINT_PARSE_ALLOW_UNDERSCORE;

  const lit_utf8_byte_t *string_end_p = string_p + size;
  string_p = ecma_string_trim_front (string_p, string_p + size);
  size = (lit_utf8_size_t) (string_end_p - string_p);

  if (size >= 3 && string_p[0] == LIT_CHAR_0)
  {
    if (string_p[1] == LIT_CHAR_LOWERCASE_X || string_p[1] == LIT_CHAR_UPPERCASE_X)
    {
      radix = 16;
      string_p += 2;
      size -= 2;
    }
    else if (string_p[1] == LIT_CHAR_LOWERCASE_O || string_p[1] == LIT_CHAR_UPPERCASE_O)
    {
      radix = 8;
      string_p += 2;
      size -= 2;
    }
    else if (string_p[1] == LIT_CHAR_LOWERCASE_B || string_p[1] == LIT_CHAR_UPPERCASE_B)
    {
      radix = 2;
      string_p += 2;
      size -= 2;
    }
  }
  else if (size >= 2)
  {
    if (string_p[0] == LIT_CHAR_PLUS)
    {
      size--;
      string_p++;
    }
    else if (string_p[0] == LIT_CHAR_MINUS)
    {
      sign = ECMA_BIGINT_SIGN;
      size--;
      string_p++;
    }
  }
  else if (size == 0)
  {
    return ECMA_BIGINT_ZERO;
  }

  while (string_p < string_end_p && (*string_p == LIT_CHAR_0 || (*string_p == LIT_CHAR_UNDERSCORE && allow_underscore)))
  {
    string_p++;
  }

  ecma_extended_primitive_t *result_p = NULL;

  if (string_p == string_end_p)
  {
    return ECMA_BIGINT_ZERO;
  }

  do
  {
    ecma_bigint_digit_t digit = radix;

    if (*string_p >= LIT_CHAR_0 && *string_p <= LIT_CHAR_9)
    {
      digit = (ecma_bigint_digit_t) (*string_p - LIT_CHAR_0);
    }
    else if (*string_p == LIT_CHAR_UNDERSCORE && allow_underscore)
    {
      continue;
    }
    else
    {
      lit_utf8_byte_t character = (lit_utf8_byte_t) LEXER_TO_ASCII_LOWERCASE (*string_p);

      if (character >= LIT_CHAR_LOWERCASE_A && character <= LIT_CHAR_LOWERCASE_F)
      {
        digit = (ecma_bigint_digit_t) (character - (LIT_CHAR_LOWERCASE_A - 10));
      }
      else if (ecma_string_trim_front (string_p, string_end_p) == string_end_p)
      {
        string_p = string_end_p;
        break;
      }
    }
    if (digit >= radix)
    {
      if (result_p != NULL)
      {
        ecma_deref_bigint (result_p);
      }

      if (options & ECMA_BIGINT_PARSE_DISALLOW_SYNTAX_ERROR)
      {
        return ECMA_VALUE_FALSE;
      }
      return ecma_raise_syntax_error (ECMA_ERR_MSG ("String cannot be converted to BigInt value"));
    }

    result_p = ecma_big_uint_mul_digit (result_p, radix, digit);

    if (JERRY_UNLIKELY (result_p == NULL))
    {
      break;
    }
  }
  while (++string_p < string_end_p);

  if (JERRY_UNLIKELY (result_p == NULL))
  {
    if (options & ECMA_BIGINT_PARSE_DISALLOW_MEMORY_ERROR)
    {
      return ECMA_VALUE_NULL;
    }
    return ecma_bigint_raise_memory_error ();
  }

  result_p->u.bigint_sign_and_size |= sign;
  return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_parse_string */

/**
 * Parse a string value and create a BigInt value
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_parse_string_value (ecma_value_t string, /**< ecma string */
                                uint32_t options) /**< ecma_bigint_parse_string_options_t option bits */
{
  JERRY_ASSERT (ecma_is_value_string (string));

  ECMA_STRING_TO_UTF8_STRING (ecma_get_string_from_value (string), string_buffer_p, string_buffer_size);

  ecma_value_t result = ecma_bigint_parse_string (string_buffer_p, string_buffer_size, options);
  ECMA_FINALIZE_UTF8_STRING (string_buffer_p, string_buffer_size);

  return result;
} /* ecma_bigint_parse_string_value */

/**
 * Create a string representation for a BigInt value
 *
 * @return ecma string or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_string_t *
ecma_bigint_to_string (ecma_value_t value, /**< BigInt value */
                       ecma_bigint_digit_t radix) /**< conversion radix */
{
  JERRY_ASSERT (ecma_is_value_bigint (value));

  if (value == ECMA_BIGINT_ZERO)
  {
    return ecma_new_ecma_string_from_code_unit (LIT_CHAR_0);
  }

  uint32_t char_start_p, char_size_p;
  ecma_extended_primitive_t *bigint_p = ecma_get_extended_primitive_from_value (value);
  lit_utf8_byte_t *string_buffer_p = ecma_big_uint_to_string (bigint_p, radix, &char_start_p, &char_size_p);

  if (JERRY_UNLIKELY (string_buffer_p == NULL))
  {
    ecma_raise_range_error (ECMA_ERR_MSG ("Cannot allocate memory for a string representation of a BigInt value"));
    return NULL;
  }

  JERRY_ASSERT (char_start_p > 0);

  if (bigint_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN)
  {
    string_buffer_p[--char_start_p] = LIT_CHAR_MINUS;
  }

  ecma_string_t *string_p;
  string_p = ecma_new_ecma_string_from_utf8 (string_buffer_p + char_start_p, char_size_p - char_start_p);

  jmem_heap_free_block (string_buffer_p, char_size_p);
  return string_p;
} /* ecma_bigint_to_string */

/**
 * Get the size of zero digits from the result of ecma_bigint_number_to_digits
 */
#define ECMA_BIGINT_NUMBER_TO_DIGITS_GET_ZERO_SIZE(value) \
  (((value) & 0xffff) * (uint32_t) sizeof (ecma_bigint_digit_t))

/**
 * Get the number of digits from the result of ecma_bigint_number_to_digits
 */
#define ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS(value) ((value) >> 20)

/**
 * Get the size of digits from the result of ecma_bigint_number_to_digits
 */
#define ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS_SIZE(value) \
  (ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS (value) * (uint32_t) sizeof (ecma_bigint_digit_t))

/**
 * Set number of digits in the result of ecma_bigint_number_to_digits
 */
#define ECMA_BIGINT_NUMBER_TO_DIGITS_SET_DIGITS(value) ((uint32_t) (value) << 20)

/**
 * This flag is set when the number passed to ecma_bigint_number_to_digits has fraction part
 */
#define ECMA_BIGINT_NUMBER_TO_DIGITS_HAS_FRACTION 0x10000

/**
 * Convert a number to maximum of 3 digits and left shift
 *
 * @return packed value, ECMA_BIGINT_NUMBER_TO_DIGITS* macros can be used to decode it
 */
static uint32_t
ecma_bigint_number_to_digits (ecma_number_t number, /**< ecma number */
                              ecma_bigint_digit_t *digits_p) /**< [out] BigInt digits */
{
  uint32_t biased_exp;
  uint64_t fraction;

  ecma_number_unpack (number, NULL, &biased_exp, &fraction);

  if (biased_exp == 0 && fraction == 0)
  {
    /* Number is zero. */
    return ECMA_BIGINT_NUMBER_TO_DIGITS_SET_DIGITS (0);
  }

  if (biased_exp < ((1 << (ECMA_NUMBER_BIASED_EXP_WIDTH - 1)) - 1))
  {
    /* Number is less than 1. */
    return ECMA_BIGINT_NUMBER_TO_DIGITS_SET_DIGITS (0) | ECMA_BIGINT_NUMBER_TO_DIGITS_HAS_FRACTION;
  }

  biased_exp -= ((1 << (ECMA_NUMBER_BIASED_EXP_WIDTH - 1)) - 1);
  fraction |= ((uint64_t) 1) << ECMA_NUMBER_FRACTION_WIDTH;

  if (biased_exp <= ECMA_NUMBER_FRACTION_WIDTH)
  {
    uint32_t has_fraction = 0;

    if (biased_exp < ECMA_NUMBER_FRACTION_WIDTH
        && (fraction << (biased_exp + ((8 * sizeof (uint64_t)) - ECMA_NUMBER_FRACTION_WIDTH))) != 0)
    {
      has_fraction |= ECMA_BIGINT_NUMBER_TO_DIGITS_HAS_FRACTION;
    }

    fraction >>= ECMA_NUMBER_FRACTION_WIDTH - biased_exp;
    digits_p[0] = (ecma_bigint_digit_t) fraction;

#if JERRY_NUMBER_TYPE_FLOAT64
    digits_p[1] = (ecma_bigint_digit_t) (fraction >> (8 * sizeof (ecma_bigint_digit_t)));
    return ECMA_BIGINT_NUMBER_TO_DIGITS_SET_DIGITS (digits_p[1] == 0 ? 1 : 2) | has_fraction;
#else /* !JERRY_NUMBER_TYPE_FLOAT64 */
    return ECMA_BIGINT_NUMBER_TO_DIGITS_SET_DIGITS (1) | has_fraction;
#endif /* JERRY_NUMBER_TYPE_FLOAT64 */
  }

  digits_p[0] = (ecma_bigint_digit_t) fraction;
#if JERRY_NUMBER_TYPE_FLOAT64
  digits_p[1] = (ecma_bigint_digit_t) (fraction >> (8 * sizeof (ecma_bigint_digit_t)));
#endif /* JERRY_NUMBER_TYPE_FLOAT64 */

  biased_exp -= ECMA_NUMBER_FRACTION_WIDTH;

  uint32_t shift_left = biased_exp & ((8 * sizeof (ecma_bigint_digit_t)) - 1);
  biased_exp = biased_exp >> ECMA_BIGINT_DIGIT_SHIFT;

  if (shift_left == 0)
  {
#if JERRY_NUMBER_TYPE_FLOAT64
    return biased_exp | ECMA_BIGINT_NUMBER_TO_DIGITS_SET_DIGITS (2);
#else /* !JERRY_NUMBER_TYPE_FLOAT64 */
    return biased_exp | ECMA_BIGINT_NUMBER_TO_DIGITS_SET_DIGITS (1);
#endif /* JERRY_NUMBER_TYPE_FLOAT64 */
  }

  uint32_t shift_right = (1 << ECMA_BIGINT_DIGIT_SHIFT) - shift_left;

#if JERRY_NUMBER_TYPE_FLOAT64
  digits_p[2] = digits_p[1] >> shift_right;
  digits_p[1] = (digits_p[1] << shift_left) | (digits_p[0] >> shift_right);
  digits_p[0] <<= shift_left;

  return biased_exp | ECMA_BIGINT_NUMBER_TO_DIGITS_SET_DIGITS (digits_p[2] == 0 ? 2 : 3);
#else /* !JERRY_NUMBER_TYPE_FLOAT64 */
  digits_p[1] = digits_p[0] >> shift_right;
  digits_p[0] <<= shift_left;

  return biased_exp | ECMA_BIGINT_NUMBER_TO_DIGITS_SET_DIGITS (digits_p[1] == 0 ? 1 : 2);
#endif /* JERRY_NUMBER_TYPE_FLOAT64 */
} /* ecma_bigint_number_to_digits */

/**
 * Convert an ecma number to BigInt value
 *
 * See also:
 *          ECMA-262 v11, 20.2.1.1.1
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
static ecma_value_t
ecma_bigint_number_to_bigint (ecma_number_t number) /**< ecma number */
{
  if (ecma_number_is_nan (number) || ecma_number_is_infinity (number))
  {
    return ecma_raise_range_error (ECMA_ERR_MSG ("Infinity or NaN cannot be converted to BigInt"));
  }

  ecma_bigint_digit_t digits[3];
  uint32_t result = ecma_bigint_number_to_digits (number, digits);

  JERRY_ASSERT (ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS (result) == 0
                || digits[ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS (result) - 1] > 0);

  if (result & ECMA_BIGINT_NUMBER_TO_DIGITS_HAS_FRACTION)
  {
    return ecma_raise_range_error (ECMA_ERR_MSG ("Only integer numbers can be converted to BigInt"));
  }

  uint32_t digits_size = ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS_SIZE (result);

  if (digits_size == 0)
  {
    return ECMA_BIGINT_ZERO;
  }

  uint32_t zero_size = ECMA_BIGINT_NUMBER_TO_DIGITS_GET_ZERO_SIZE (result);

  ecma_extended_primitive_t *result_p = ecma_bigint_create (digits_size + zero_size);

  if (JERRY_UNLIKELY (result_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  uint8_t *data_p = (uint8_t *) ECMA_BIGINT_GET_DIGITS (result_p, 0);
  memset (data_p, 0, zero_size);
  memcpy (data_p + zero_size, digits, digits_size);

  if (number < 0)
  {
    result_p->u.bigint_sign_and_size |= ECMA_BIGINT_SIGN;
  }

  return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_number_to_bigint */

/**
 * Convert a value to BigInt value
 *
 * See also:
 *          ECMA-262 v11, 7.1.13
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_to_bigint (ecma_value_t value, /**< any value */
                       bool allow_numbers) /**< converting ecma numbers is allowed */
{
  bool free_value = false;

  if (ecma_is_value_object (value))
  {
    value = ecma_op_object_default_value (ecma_get_object_from_value (value), ECMA_PREFERRED_TYPE_NUMBER);
    free_value = true;

    if (ECMA_IS_VALUE_ERROR (value))
    {
      return value;
    }
  }

  ecma_value_t result;

  if (ecma_is_value_string (value))
  {
    result = ecma_bigint_parse_string_value (value, ECMA_BIGINT_PARSE_NO_OPTIONS);
  }
  else if (ecma_is_value_bigint (value))
  {
    result = value;

    if (!free_value && value != ECMA_BIGINT_ZERO)
    {
      ecma_ref_extended_primitive (ecma_get_extended_primitive_from_value (value));
    }
    else
    {
      free_value = false;
    }
  }
  else if (allow_numbers && ecma_is_value_number (value))
  {
    result = ecma_bigint_number_to_bigint (ecma_get_number_from_value (value));
  }
  else if (ecma_is_value_false (value))
  {
    result = ECMA_BIGINT_ZERO;
  }
  else if (ecma_is_value_true (value))
  {
    result = ecma_bigint_create_from_digit (1, false);
  }
  else
  {
    result = ecma_raise_type_error (ECMA_ERR_MSG ("Value cannot be converted to BigInt"));
  }

  if (free_value)
  {
    ecma_free_value (value);
  }

  return result;
} /* ecma_bigint_to_bigint */

/**
 * Convert a BigInt value to number value
 *
 * @return ecma number value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_to_number (ecma_value_t value) /**< BigInt value */
{
  JERRY_ASSERT (ecma_is_value_bigint (value));

  if (value == ECMA_BIGINT_ZERO)
  {
    return ecma_make_integer_value (0);
  }

  ecma_extended_primitive_t *value_p = ecma_get_extended_primitive_from_value (value);
  uint32_t size = ECMA_BIGINT_GET_SIZE (value_p);
  ecma_bigint_digit_t *digits_p = ECMA_BIGINT_GET_DIGITS (value_p, size);

  if (size == sizeof (ecma_bigint_digit_t))
  {
    if (!(value_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN))
    {
      if (digits_p[-1] <= ECMA_INTEGER_NUMBER_MAX)
      {
        return ecma_make_integer_value ((ecma_integer_value_t) digits_p[-1]);
      }
    }
    else if (digits_p[-1] <= -ECMA_INTEGER_NUMBER_MIN)
    {
      return ecma_make_integer_value (-(ecma_integer_value_t) digits_p[-1]);
    }
  }

  uint64_t fraction = 0;
  ecma_bigint_digit_t shift_left;

  if (digits_p[-1] == 1)
  {
    JERRY_ASSERT (size > sizeof (ecma_bigint_digit_t));

    fraction = ((uint64_t) digits_p[-2]) << (8 * sizeof (ecma_bigint_digit_t));
    shift_left = (uint32_t) (8 * sizeof (ecma_bigint_digit_t));

    if (size >= 3 * sizeof (ecma_bigint_digit_t))
    {
      fraction |= (uint64_t) digits_p[-3];
    }
  }
  else
  {
    shift_left = ecma_big_uint_count_leading_zero (digits_p[-1]) + 1;

    fraction = ((uint64_t) digits_p[-1]) << (8 * sizeof (ecma_bigint_digit_t) + shift_left);

    if (size >= 2 * sizeof (ecma_bigint_digit_t))
    {
      fraction |= ((uint64_t) digits_p[-2]) << shift_left;
    }

    if (size >= 3 * sizeof (ecma_bigint_digit_t))
    {
      fraction |= ((uint64_t) digits_p[-3]) >> (8 * sizeof (ecma_bigint_digit_t) - shift_left);
    }
  }

  uint32_t biased_exp = (uint32_t) (((1 << (ECMA_NUMBER_BIASED_EXP_WIDTH - 1)) - 1) + (size * 8 - shift_left));

  /* Rounding result. */
  const uint64_t rounding_bit = (((uint64_t) 1) << (8 * sizeof (uint64_t) - ECMA_NUMBER_FRACTION_WIDTH - 1));
  bool round_up = false;

  if (fraction & rounding_bit)
  {
    round_up = true;

    /* IEEE_754 roundTiesToEven mode: when rounding_bit is set, and all the remaining bits
     * are zero, the number needs to be rounded down the bit before rounding_bit is zero. */
    if ((fraction & ((rounding_bit << 2) - 1)) == rounding_bit)
    {
      round_up = false;

      if ((size >= (3 * sizeof (ecma_bigint_digit_t)))
          && (shift_left == (8 * sizeof (ecma_bigint_digit_t))
              || (digits_p[-3] & ((((ecma_bigint_digit_t) 1) << shift_left) - 1)) == 0))
      {
        ecma_bigint_digit_t *digits_start_p = ECMA_BIGINT_GET_DIGITS (value_p, 0);

        digits_p -= 3;

        while (digits_p > digits_start_p)
        {
          if (digits_p[-1] != 0)
          {
            round_up = true;
            break;
          }
          digits_p--;
        }
      }
    }
  }

  if (round_up)
  {
    fraction += rounding_bit;
    fraction >>= (8 * sizeof (uint64_t) - ECMA_NUMBER_FRACTION_WIDTH);

    if (fraction == 0)
    {
      biased_exp++;
    }
  }
  else
  {
    fraction >>= (8 * sizeof (uint64_t) - ECMA_NUMBER_FRACTION_WIDTH);
  }

  bool sign = (value_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN);
  ecma_number_t result;

  if (biased_exp < (1u << ECMA_NUMBER_BIASED_EXP_WIDTH) - 1)
  {
    result = ecma_number_pack (sign, biased_exp, fraction);
  }
  else
  {
    result = ecma_number_make_infinity (sign);
  }

  return ecma_make_number_value (result);
} /* ecma_bigint_to_number */

/**
 * Returns with a BigInt if the value is BigInt,
 * or the value is object, and its default value is BigInt
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_get_bigint (ecma_value_t value, /**< any value */
                        bool *free_result_p) /**< [out] result should be freed */
{
  *free_result_p = false;

  if (ecma_is_value_bigint (value))
  {
    return value;
  }

  if (ecma_is_value_object (value))
  {
    ecma_object_t *object_p = ecma_get_object_from_value (value);
    ecma_value_t default_value = ecma_op_object_default_value (object_p, ECMA_PREFERRED_TYPE_NUMBER);

    if (ECMA_IS_VALUE_ERROR (default_value))
    {
      return default_value;
    }

    if (ecma_is_value_bigint (default_value))
    {
      *free_result_p = (default_value != ECMA_BIGINT_ZERO);
      return default_value;
    }

    ecma_free_value (default_value);
  }

  return ecma_raise_type_error (ECMA_ERR_MSG ("Cannot convert a BigInt value to a number"));
} /* ecma_bigint_get_bigint */

/**
 * Create BigInt value from uint64 digits
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_create_from_digits (const uint64_t *digits_p, /**< BigInt digits */
                                uint32_t size, /**< number of BigInt digits */
                                bool sign) /**< sign bit, true if the result should be negative */
{
  const uint64_t *digits_end_p = digits_p + size;

  while (digits_end_p > digits_p && digits_end_p[-1] == 0)
  {
    digits_end_p--;
  }

  if (digits_p == digits_end_p)
  {
    return ECMA_BIGINT_ZERO;
  }

  size = (uint32_t) (digits_end_p - digits_p);

  if (size < ECMA_BIGINT_MAX_SIZE)
  {
    size *= (uint32_t) sizeof (uint64_t);
  }

  if ((digits_end_p[-1] >> (8 * sizeof (ecma_bigint_digit_t))) == 0)
  {
    size -= (uint32_t) sizeof (ecma_bigint_digit_t);
  }

  ecma_extended_primitive_t *result_value_p = ecma_bigint_create (size);

  if (JERRY_UNLIKELY (result_value_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  if (sign)
  {
    result_value_p->u.bigint_sign_and_size |= ECMA_BIGINT_SIGN;
  }

  ecma_bigint_digit_t *result_p = ECMA_BIGINT_GET_DIGITS (result_value_p, 0);

  while (digits_p < digits_end_p)
  {
    uint64_t digit = *digits_p++;

    result_p[0] = (ecma_bigint_digit_t) digit;
    result_p[1] = (ecma_bigint_digit_t) (digit >> (8 * sizeof (ecma_bigint_digit_t)));
    result_p+= 2;
  }

  return ecma_make_extended_primitive_value (result_value_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_create_from_digits */

/**
 * Get the number of uint64 digits of a BigInt value
 *
 * @return number of uint64 digits
 */
uint32_t
ecma_bigint_get_size_in_digits (ecma_value_t value) /**< BigInt value */
{
  JERRY_ASSERT (ecma_is_value_bigint (value));

  if (value == ECMA_BIGINT_ZERO)
  {
    return 0;
  }

  ecma_extended_primitive_t *value_p = ecma_get_extended_primitive_from_value (value);
  uint32_t size = ECMA_BIGINT_GET_SIZE (value_p);

  return (size + (uint32_t) sizeof (ecma_bigint_digit_t)) / sizeof (uint64_t);
} /* ecma_bigint_get_size_in_digits */

/**
 * Get the uint64 digits of a BigInt value
 */
void
ecma_bigint_get_digits_and_sign (ecma_value_t value, /**< BigInt value */
                                 uint64_t *digits_p, /**< [out] buffer for digits */
                                 uint32_t size, /**< buffer size in digits */
                                 bool *sign_p) /**< [out] sign of BigInt */
{
  JERRY_ASSERT (ecma_is_value_bigint (value));

  if (value == ECMA_BIGINT_ZERO)
  {
    if (sign_p != NULL)
    {
      *sign_p = false;
    }
    memset (digits_p, 0, size * sizeof (uint64_t));
    return;
  }

  ecma_extended_primitive_t *value_p = ecma_get_extended_primitive_from_value (value);

  if (sign_p != NULL)
  {
    *sign_p = (value_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN) != 0;
  }

  uint32_t bigint_size = ECMA_BIGINT_GET_SIZE (value_p);
  uint32_t copy_size = bigint_size / sizeof (uint64_t);

  if (copy_size > size)
  {
    copy_size = size;
  }

  const uint64_t *digits_end_p = digits_p + copy_size;
  ecma_bigint_digit_t *source_p = ECMA_BIGINT_GET_DIGITS (value_p, 0);

  while (digits_p < digits_end_p)
  {
    *digits_p++ = source_p[0] | (((uint64_t) source_p[1]) << (8 * sizeof (ecma_bigint_digit_t)));
    source_p += 2;
  }

  size -= copy_size;

  if (size == 0)
  {
    return;
  }

  if (ECMA_BIGINT_SIZE_IS_ODD (bigint_size))
  {
    *digits_p++ = source_p[0];
    size--;
  }

  if (size > 0)
  {
    memset (digits_p, 0, size * sizeof (uint64_t));
  }
} /* ecma_bigint_get_digits_and_sign */

/**
 * Compare two BigInt values
 *
 * @return true if they are the same, false otherwise
 */
bool
ecma_bigint_is_equal_to_bigint (ecma_value_t left_value, /**< left BigInt value */
                                ecma_value_t right_value) /**< right BigInt value */
{
  JERRY_ASSERT (ecma_is_value_bigint (left_value) && ecma_is_value_bigint (right_value));

  if (left_value == ECMA_BIGINT_ZERO)
  {
    return right_value == ECMA_BIGINT_ZERO;
  }
  else if (right_value == ECMA_BIGINT_ZERO)
  {
    return false;
  }

  ecma_extended_primitive_t *left_p = ecma_get_extended_primitive_from_value (left_value);
  ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);

  if (left_p->u.bigint_sign_and_size != right_p->u.bigint_sign_and_size)
  {
    return false;
  }

  uint32_t size = ECMA_BIGINT_GET_SIZE (left_p);
  return memcmp (ECMA_BIGINT_GET_DIGITS (left_p, 0), ECMA_BIGINT_GET_DIGITS (right_p, 0), size) == 0;
} /* ecma_bigint_is_equal_to_bigint */

/**
 * Compare a BigInt value and a number
 *
 * @return true if they are the same, false otherwise
 */
bool
ecma_bigint_is_equal_to_number (ecma_value_t left_value, /**< left BigInt value */
                                ecma_number_t right_value) /**< right number value */
{
  JERRY_ASSERT (ecma_is_value_bigint (left_value));

  if (ecma_number_is_nan (right_value) || ecma_number_is_infinity (right_value))
  {
    return false;
  }

  if (left_value == ECMA_BIGINT_ZERO)
  {
    return right_value == 0;
  }

  ecma_extended_primitive_t *left_value_p = ecma_get_extended_primitive_from_value (left_value);

  /* Sign must be the same. */
  if (left_value_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN)
  {
    if (right_value > 0)
    {
      return false;
    }
  }
  else if (right_value < 0)
  {
    return false;
  }

  ecma_bigint_digit_t digits[3];
  uint32_t result = ecma_bigint_number_to_digits (right_value, digits);

  JERRY_ASSERT (ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS (result) == 0
                || digits[ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS (result) - 1] > 0);

  if (result & ECMA_BIGINT_NUMBER_TO_DIGITS_HAS_FRACTION)
  {
    return false;
  }

  uint32_t digits_size = ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS_SIZE (result);
  uint32_t zero_size = ECMA_BIGINT_NUMBER_TO_DIGITS_GET_ZERO_SIZE (result);

  if (ECMA_BIGINT_GET_SIZE (left_value_p) != digits_size + zero_size)
  {
    return false;
  }

  ecma_bigint_digit_t *left_p = ECMA_BIGINT_GET_DIGITS (left_value_p, 0);
  ecma_bigint_digit_t *left_end_p = (ecma_bigint_digit_t *) (((uint8_t *) left_p) + zero_size);

  /* Check value bits first. */
  if (memcmp (left_end_p, digits, digits_size) != 0)
  {
    return false;
  }

  while (left_p < left_end_p)
  {
    if (*left_p++ != 0)
    {
      return false;
    }
  }

  return true;
} /* ecma_bigint_is_equal_to_number */

/**
 * Convert 0 to 1, and 1 to -1. Useful for getting sign.
 */
#define ECMA_BIGINT_TO_SIGN(value) (1 - (((int) (value)) << 1))

/**
 * Convert 0 to -1, and 1 to 1. Useful for getting negated sign.
 */
#define ECMA_BIGINT_TO_NEGATED_SIGN(value) (-1 + (((int) (value)) << 1))

/**
 * Compare two BigInt values
 *
 * return -1, if left value < right value, 0 if they are equal, 1 otherwise
 */
int
ecma_bigint_compare_to_bigint (ecma_value_t left_value, /**< left BigInt value */
                               ecma_value_t right_value) /**< right BigInt value */
{
  JERRY_ASSERT (ecma_is_value_bigint (left_value) && ecma_is_value_bigint (right_value));

  if (left_value == ECMA_BIGINT_ZERO)
  {
    if (right_value == ECMA_BIGINT_ZERO)
    {
      return 0;
    }

    ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);
    return ECMA_BIGINT_TO_NEGATED_SIGN (right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN);
  }

  ecma_extended_primitive_t *left_p = ecma_get_extended_primitive_from_value (left_value);
  uint32_t left_sign = left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN;

  if (right_value == ECMA_BIGINT_ZERO)
  {
    return ECMA_BIGINT_TO_SIGN (left_sign);
  }

  ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);
  uint32_t right_sign = right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN;

  if ((left_sign ^ right_sign) != 0)
  {
    return ECMA_BIGINT_TO_SIGN (left_sign);
  }

  if (left_sign == 0)
  {
    return ecma_big_uint_compare (left_p, right_p);
  }

  return -ecma_big_uint_compare (left_p, right_p);
} /* ecma_bigint_compare_to_bigint */

/**
 * Compare a BigInt value and a number
 *
 * return -1, if left value < right value, 0 if they are equal, 1 otherwise
 */
int
ecma_bigint_compare_to_number (ecma_value_t left_value, /**< left BigInt value */
                               ecma_number_t right_value) /**< right number value */
{
  JERRY_ASSERT (ecma_is_value_bigint (left_value));
  JERRY_ASSERT (!ecma_number_is_nan (right_value));

  int right_invert_sign = ECMA_BIGINT_TO_SIGN (right_value > 0);

  if (left_value == ECMA_BIGINT_ZERO)
  {
    if (right_value == 0)
    {
      return 0;
    }

    return right_invert_sign;
  }

  ecma_extended_primitive_t *left_value_p = ecma_get_extended_primitive_from_value (left_value);
  int left_sign = ECMA_BIGINT_TO_SIGN (left_value_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN);

  if (right_value == 0 || left_sign == right_invert_sign)
  {
    /* Second condition: a positive BigInt is always greater than any negative number, and the opposite is true. */
    return left_sign;
  }

  if (ecma_number_is_infinity (right_value))
  {
    /* Infinity is always bigger than any BigInt number. */
    return right_invert_sign;
  }

  ecma_bigint_digit_t digits[3];
  uint32_t result = ecma_bigint_number_to_digits (right_value, digits);

  JERRY_ASSERT (ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS (result) == 0
                || digits[ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS (result) - 1] > 0);

  uint32_t digits_size = ECMA_BIGINT_NUMBER_TO_DIGITS_GET_DIGITS_SIZE (result);

  if (digits_size == 0)
  {
    JERRY_ASSERT (result & ECMA_BIGINT_NUMBER_TO_DIGITS_HAS_FRACTION);
    /* The number is between [-1 .. 1] exclusive. */
    return left_sign;
  }

  uint32_t left_size = ECMA_BIGINT_GET_SIZE (left_value_p);
  uint32_t right_size = digits_size + ECMA_BIGINT_NUMBER_TO_DIGITS_GET_ZERO_SIZE (result);

  if (left_size != right_size)
  {
    return left_size > right_size ? left_sign : -left_sign;
  }

  ecma_bigint_digit_t *left_p = ECMA_BIGINT_GET_DIGITS (left_value_p, right_size);
  ecma_bigint_digit_t *left_end_p = (ecma_bigint_digit_t *) (((uint8_t *) left_p) - digits_size);
  ecma_bigint_digit_t *digits_p = (ecma_bigint_digit_t *) (((uint8_t *) digits) + digits_size);

  do
  {
    ecma_bigint_digit_t left = *(--left_p);
    ecma_bigint_digit_t right = *(--digits_p);

    if (left != right)
    {
      return left > right ? left_sign : -left_sign;
    }
  }
  while (left_p > left_end_p);

  left_end_p = ECMA_BIGINT_GET_DIGITS (left_value_p, 0);

  while (left_p > left_end_p)
  {
    if (*(--left_p) != 0)
    {
      return left_sign;
    }
  }

  return (result & ECMA_BIGINT_NUMBER_TO_DIGITS_HAS_FRACTION) ? -left_sign : 0;
} /* ecma_bigint_compare_to_number */

#undef ECMA_BIGINT_TO_SIGN

/**
 * Negate a non-zero BigInt value
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_negate (ecma_extended_primitive_t *value_p) /**< BigInt value */
{
  uint32_t size = ECMA_BIGINT_GET_SIZE (value_p);

  JERRY_ASSERT (size > 0 && ECMA_BIGINT_GET_LAST_DIGIT (value_p, size) != 0);

  ecma_extended_primitive_t *result_p = ecma_bigint_create (size);

  if (JERRY_UNLIKELY (result_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  memcpy (result_p + 1, value_p + 1, size);
  result_p->refs_and_type = ECMA_EXTENDED_PRIMITIVE_REF_ONE | ECMA_TYPE_BIGINT;
  result_p->u.bigint_sign_and_size = value_p->u.bigint_sign_and_size ^ ECMA_BIGINT_SIGN;

  return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_negate */

/**
 * Invert all bits of a BigInt value
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_unary (ecma_value_t value, /**< BigInt value */
                   ecma_bigint_unary_operation_type type) /**< type of unary operation */
{
  JERRY_ASSERT (ecma_is_value_bigint (value));

  if (value == ECMA_BIGINT_ZERO)
  {
    return ecma_bigint_create_from_digit (1, type != ECMA_BIGINT_UNARY_INCREASE);
  }

  ecma_extended_primitive_t *value_p = ecma_get_extended_primitive_from_value (value);

  uint32_t sign = (type != ECMA_BIGINT_UNARY_DECREASE) ? ECMA_BIGINT_SIGN : 0;

  if ((value_p->u.bigint_sign_and_size == (uint32_t) (sizeof (ecma_bigint_digit_t) | sign))
      && *ECMA_BIGINT_GET_DIGITS (value_p, 0) == 1)
  {
    return ECMA_BIGINT_ZERO;
  }

  ecma_extended_primitive_t *result_p;

  if ((value_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN) == (sign ^ ECMA_BIGINT_SIGN))
  {
    result_p = ecma_big_uint_increase (value_p);

    if (type != ECMA_BIGINT_UNARY_INCREASE && result_p != NULL)
    {
      result_p->u.bigint_sign_and_size |= ECMA_BIGINT_SIGN;
    }
  }
  else
  {
    result_p = ecma_big_uint_decrease (value_p);

    if (type == ECMA_BIGINT_UNARY_INCREASE && result_p != NULL)
    {
      result_p->u.bigint_sign_and_size |= ECMA_BIGINT_SIGN;
    }
  }

  if (JERRY_UNLIKELY (result_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_unary */

/**
 * Add/subtract right BigInt value to/from left BigInt value
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_add_sub (ecma_value_t left_value, /**< left BigInt value */
                     ecma_value_t right_value, /**< right BigInt value */
                     bool is_add) /**< true if add operation should be performed */
{
  JERRY_ASSERT (ecma_is_value_bigint (left_value) && ecma_is_value_bigint (right_value));

  if (right_value == ECMA_BIGINT_ZERO)
  {
    return ecma_copy_value (left_value);
  }

  ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);

  if (left_value == ECMA_BIGINT_ZERO)
  {
    if (!is_add)
    {
      return ecma_bigint_negate (right_p);
    }

    ecma_ref_extended_primitive (right_p);
    return right_value;
  }

  ecma_extended_primitive_t *left_p = ecma_get_extended_primitive_from_value (left_value);
  uint32_t sign = is_add ? 0 : ECMA_BIGINT_SIGN;

  if (((left_p->u.bigint_sign_and_size ^ right_p->u.bigint_sign_and_size) & ECMA_BIGINT_SIGN) == sign)
  {
    ecma_extended_primitive_t *result_p = ecma_big_uint_add (left_p, right_p);

    if (JERRY_UNLIKELY (result_p == NULL))
    {
      return ecma_bigint_raise_memory_error ();
    }

    result_p->u.bigint_sign_and_size |= left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN;
    return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
  }

  int compare_result = ecma_big_uint_compare (left_p, right_p);
  ecma_extended_primitive_t *result_p;

  if (compare_result == 0)
  {
    return ECMA_BIGINT_ZERO;
  }

  if (compare_result > 0)
  {
    sign = left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN;
    result_p = ecma_big_uint_sub (left_p, right_p);
  }
  else
  {
    sign = right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN;

    if (!is_add)
    {
      sign ^= ECMA_BIGINT_SIGN;
    }

    result_p = ecma_big_uint_sub (right_p, left_p);
  }

  if (JERRY_UNLIKELY (result_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  result_p->u.bigint_sign_and_size |= sign;
  return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_add_sub */

/**
 * Multiply two BigInt values
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_mul (ecma_value_t left_value, /**< left BigInt value */
                 ecma_value_t right_value) /**< right BigInt value */
{
  JERRY_ASSERT (ecma_is_value_bigint (left_value) && ecma_is_value_bigint (right_value));

  if (left_value == ECMA_BIGINT_ZERO || right_value == ECMA_BIGINT_ZERO)
  {
    return ECMA_BIGINT_ZERO;
  }

  ecma_extended_primitive_t *left_p = ecma_get_extended_primitive_from_value (left_value);
  ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);
  uint32_t left_size = ECMA_BIGINT_GET_SIZE (left_p);
  uint32_t right_size = ECMA_BIGINT_GET_SIZE (right_p);

  if (left_size == sizeof (ecma_bigint_digit_t)
      && ECMA_BIGINT_GET_LAST_DIGIT (left_p, sizeof (ecma_bigint_digit_t)) == 1)
  {
    if (left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN)
    {
      return ecma_bigint_negate (right_p);
    }

    ecma_ref_extended_primitive (right_p);
    return right_value;
  }

  if (right_size == sizeof (ecma_bigint_digit_t)
      && ECMA_BIGINT_GET_LAST_DIGIT (right_p, sizeof (ecma_bigint_digit_t)) == 1)
  {
    if (right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN)
    {
      return ecma_bigint_negate (left_p);
    }

    ecma_ref_extended_primitive (left_p);
    return left_value;
  }

  ecma_extended_primitive_t *result_p = ecma_big_uint_mul (left_p, right_p);

  if (JERRY_UNLIKELY (result_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  uint32_t sign = (left_p->u.bigint_sign_and_size ^ right_p->u.bigint_sign_and_size) & ECMA_BIGINT_SIGN;
  result_p->u.bigint_sign_and_size |= sign;
  return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_mul */

/**
 * Divide two BigInt values
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_div_mod (ecma_value_t left_value, /**< left BigInt value */
                     ecma_value_t right_value, /**< right BigInt value */
                     bool is_mod) /**< true if return with remainder */
{
  JERRY_ASSERT (ecma_is_value_bigint (left_value) && ecma_is_value_bigint (right_value));

  if (right_value == ECMA_BIGINT_ZERO)
  {
    return ecma_raise_range_error (ECMA_ERR_MSG ("BigInt division by zero"));
  }

  if (left_value == ECMA_BIGINT_ZERO)
  {
    return left_value;
  }

  ecma_extended_primitive_t *left_p = ecma_get_extended_primitive_from_value (left_value);
  ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);

  int compare_result = ecma_big_uint_compare (left_p, right_p);
  ecma_extended_primitive_t *result_p;

  if (compare_result < 0)
  {
    if (!is_mod)
    {
      return ECMA_BIGINT_ZERO;
    }

    ecma_ref_extended_primitive (left_p);
    return left_value;
  }
  else if (compare_result == 0)
  {
    if (is_mod)
    {
      return ECMA_BIGINT_ZERO;
    }

    result_p = ecma_bigint_create (sizeof (ecma_bigint_digit_t));

    if (result_p != NULL)
    {
      *ECMA_BIGINT_GET_DIGITS (result_p, 0) = 1;
    }
  }
  else
  {
    result_p = ecma_big_uint_div_mod (left_p, right_p, is_mod);

    if (result_p == ECMA_BIGINT_POINTER_TO_ZERO)
    {
      return ECMA_BIGINT_ZERO;
    }
  }

  if (JERRY_UNLIKELY (result_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  if (is_mod)
  {
    result_p->u.bigint_sign_and_size |= left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN;
  }
  else
  {
    uint32_t sign = (left_p->u.bigint_sign_and_size ^ right_p->u.bigint_sign_and_size) & ECMA_BIGINT_SIGN;
    result_p->u.bigint_sign_and_size |= sign;
  }

  return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_div_mod */

/**
 * Shift left BigInt value to left or right
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_shift (ecma_value_t left_value, /**< left BigInt value */
                   ecma_value_t right_value, /**< right BigInt value */
                   bool is_left) /**< true if left shift operation should be performed */
{
  JERRY_ASSERT (ecma_is_value_bigint (left_value) && ecma_is_value_bigint (right_value));

  if (left_value == ECMA_BIGINT_ZERO)
  {
    return ECMA_BIGINT_ZERO;
  }

  ecma_extended_primitive_t *left_p = ecma_get_extended_primitive_from_value (left_value);

  if (right_value == ECMA_BIGINT_ZERO)
  {
    ecma_ref_extended_primitive (left_p);
    return left_value;
  }

  ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);

  if (right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN)
  {
    is_left = !is_left;
  }

  if (ECMA_BIGINT_GET_SIZE (right_p) > sizeof (ecma_bigint_digit_t))
  {
    if (is_left)
    {
      return ecma_bigint_raise_memory_error ();
    }
    else if (left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN)
    {
      /* Shifting a negative value with a very big number to the right should be -1. */
      return ecma_bigint_create_from_digit (1, true);
    }

    return ECMA_BIGINT_ZERO;
  }

  ecma_extended_primitive_t *result_p;
  ecma_bigint_digit_t shift = ECMA_BIGINT_GET_LAST_DIGIT (right_p, sizeof (ecma_bigint_digit_t));
  uint32_t left_sign = left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN;

  if (is_left)
  {
    result_p = ecma_big_uint_shift_left (left_p, shift);
  }
  else
  {
    /* -x >> y == ~(x - 1) >> y == ~((x - 1) >> y) == -(((x - 1) >> y) + 1)
     * When a non-zero bit is shifted out: (x - 1) >> y == x >> y, so the formula is -((x >> y) + 1)
     * When only zero bits are shifted out: (((x - 1) >> y) + 1) == x >> y so the formula is: -(x >> y) */
    result_p = ecma_big_uint_shift_right (left_p, shift, left_sign != 0);

    if (result_p == ECMA_BIGINT_POINTER_TO_ZERO)
    {
      return ECMA_BIGINT_ZERO;
    }
  }

  if (JERRY_UNLIKELY (result_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  result_p->u.bigint_sign_and_size |= left_sign;
  return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_shift */

#if JERRY_ESNEXT

/**
 * Compute the left value raised to the power of right value
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_pow (ecma_value_t left_value, /**< left BigInt value */
                 ecma_value_t right_value) /**< right BigInt value */
{
  JERRY_ASSERT (ecma_is_value_bigint (left_value) && ecma_is_value_bigint (right_value));

  if (right_value == ECMA_BIGINT_ZERO)
  {
    return ecma_bigint_create_from_digit (1, false);
  }

  ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);

  if (right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN)
  {
    return ecma_raise_range_error (ECMA_ERR_MSG ("Negative exponent is not allowed for BigInts"));
  }

  if (left_value == ECMA_BIGINT_ZERO)
  {
    return ECMA_BIGINT_ZERO;
  }

  ecma_extended_primitive_t *left_p = ecma_get_extended_primitive_from_value (left_value);
  ecma_bigint_digit_t base = 0;

  if (ECMA_BIGINT_GET_SIZE (left_p) == sizeof (ecma_bigint_digit_t))
  {
    base = *ECMA_BIGINT_GET_DIGITS (left_p, 0);

    JERRY_ASSERT (base != 0);

    if (base == 1)
    {
      if (!(left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN)
          || ECMA_BIGINT_NUMBER_IS_ODD (*ECMA_BIGINT_GET_DIGITS (right_p, 0)))
      {
        ecma_ref_extended_primitive (left_p);
        return left_value;
      }

      return ecma_bigint_create_from_digit (1, false);
    }
  }

  if (JERRY_UNLIKELY (ECMA_BIGINT_GET_SIZE (right_p) > sizeof (ecma_bigint_digit_t)))
  {
    return ecma_bigint_raise_memory_error ();
  }

  ecma_bigint_digit_t power = *ECMA_BIGINT_GET_DIGITS (right_p, 0);

  if (power == 1)
  {
    ecma_ref_extended_primitive (left_p);
    return left_value;
  }

  ecma_extended_primitive_t *result_p;

  if (base == 2)
  {
    result_p = ecma_big_uint_shift_left (left_p, power - 1);
  }
  else
  {
    result_p = ecma_big_uint_pow (left_p, power);
  }

  if (JERRY_UNLIKELY (result_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  if ((left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN) && ECMA_BIGINT_NUMBER_IS_ODD (power))
  {
    result_p->u.bigint_sign_and_size |= ECMA_BIGINT_SIGN;
  }

  return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_pow */

#endif /* JERRY_ESNEXT */

/**
 * Convert the result to an ecma value
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
static ecma_value_t
ecma_bigint_bitwise_op (uint32_t operation_and_options, /**< bitwise operation type and options */
                        ecma_extended_primitive_t *left_value_p, /**< left BigInt value */
                        ecma_extended_primitive_t *right_value_p) /**< right BigInt value */
{
  ecma_extended_primitive_t *result_p;
  result_p = ecma_big_uint_bitwise_op (operation_and_options, left_value_p, right_value_p);

  if (JERRY_UNLIKELY (result_p == NULL))
  {
    return ecma_bigint_raise_memory_error ();
  }

  if (result_p == ECMA_BIGINT_POINTER_TO_ZERO)
  {
    return ECMA_BIGINT_ZERO;
  }

  if (operation_and_options & ECMA_BIG_UINT_BITWISE_INCREASE_RESULT)
  {
    result_p->u.bigint_sign_and_size |= ECMA_BIGINT_SIGN;
  }

  return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
} /* ecma_bigint_bitwise_op */

/**
 * Perform bitwise 'and' operations on two BigUInt numbers
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_and (ecma_value_t left_value, /**< left BigInt value */
                 ecma_value_t right_value) /**< right BigInt value */
{
  if (left_value == ECMA_BIGINT_ZERO || right_value == ECMA_BIGINT_ZERO)
  {
    return ECMA_BIGINT_ZERO;
  }

  ecma_extended_primitive_t *left_p = ecma_get_extended_primitive_from_value (left_value);
  ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);

  if (!(left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN))
  {
    if (!(right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN))
    {
      return ecma_bigint_bitwise_op (ECMA_BIG_UINT_BITWISE_AND, left_p, right_p);
    }

    /* x & (-y) == x & ~(y-1) == x &~ (y-1) */
    uint32_t operation_and_options = ECMA_BIG_UINT_BITWISE_AND_NOT | ECMA_BIG_UINT_BITWISE_DECREASE_RIGHT;
    return ecma_bigint_bitwise_op (operation_and_options, left_p, right_p);
  }

  if (!(right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN))
  {
    /* (-x) & y == ~(x-1) & y == y &~ (x-1) */
    uint32_t operation_and_options = ECMA_BIG_UINT_BITWISE_AND_NOT | ECMA_BIG_UINT_BITWISE_DECREASE_RIGHT;
    return ecma_bigint_bitwise_op (operation_and_options, right_p, left_p);
  }

  /* (-x) & (-y) == ~(x-1) & ~(y-1) == ~((x-1) | (y-1)) == -(((x-1) | (y-1)) + 1) */
  uint32_t operation_and_options = (ECMA_BIG_UINT_BITWISE_OR
                                    | ECMA_BIG_UINT_BITWISE_DECREASE_BOTH
                                    | ECMA_BIG_UINT_BITWISE_INCREASE_RESULT);
  return ecma_bigint_bitwise_op (operation_and_options, left_p, right_p);
} /* ecma_bigint_and */

/**
 * Perform bitwise 'or' operations on two BigUInt numbers
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_or (ecma_value_t left_value, /**< left BigInt value */
                ecma_value_t right_value) /**< right BigInt value */
{
  if (left_value == ECMA_BIGINT_ZERO)
  {
    return ecma_copy_value (right_value);
  }

  if (right_value == ECMA_BIGINT_ZERO)
  {
    return ecma_copy_value (left_value);
  }

  ecma_extended_primitive_t *left_p = ecma_get_extended_primitive_from_value (left_value);
  ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);

  if (!(left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN))
  {
    if (!(right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN))
    {
      return ecma_bigint_bitwise_op (ECMA_BIG_UINT_BITWISE_OR, left_p, right_p);
    }

    /* x | (-y) == x | ~(y-1) == ~((y-1) &~ x) == -(((y-1) &~ x) + 1) */
    uint32_t operation_and_options = (ECMA_BIG_UINT_BITWISE_AND_NOT
                                      | ECMA_BIG_UINT_BITWISE_DECREASE_LEFT
                                      | ECMA_BIG_UINT_BITWISE_INCREASE_RESULT);
    return ecma_bigint_bitwise_op (operation_and_options, right_p, left_p);
  }

  if (!(right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN))
  {
    /* (-x) | y == ~(x-1) | y == ~((x-1) &~ y) == -(((x-1) &~ y) + 1) */
    uint32_t operation_and_options = (ECMA_BIG_UINT_BITWISE_AND_NOT
                                      | ECMA_BIG_UINT_BITWISE_DECREASE_LEFT
                                      | ECMA_BIG_UINT_BITWISE_INCREASE_RESULT);
    return ecma_bigint_bitwise_op (operation_and_options, left_p, right_p);
  }

  /* (-x) | (-y) == ~(x-1) | ~(y-1) == ~((x-1) & (y-1)) = -(((x-1) & (y-1)) + 1) */
  uint32_t operation_and_options = (ECMA_BIG_UINT_BITWISE_AND
                                    | ECMA_BIG_UINT_BITWISE_DECREASE_BOTH
                                    | ECMA_BIG_UINT_BITWISE_INCREASE_RESULT);
  return ecma_bigint_bitwise_op (operation_and_options, left_p, right_p);
} /* ecma_bigint_or */

/**
 * Perform bitwise 'xor' operations on two BigUInt numbers
 *
 * @return ecma BigInt value or ECMA_VALUE_ERROR
 *         Returned value must be freed with ecma_free_value.
 */
ecma_value_t
ecma_bigint_xor (ecma_value_t left_value, /**< left BigInt value */
                 ecma_value_t right_value) /**< right BigInt value */
{
  if (left_value == ECMA_BIGINT_ZERO)
  {
    return ecma_copy_value (right_value);
  }

  if (right_value == ECMA_BIGINT_ZERO)
  {
    return ecma_copy_value (left_value);
  }

  ecma_extended_primitive_t *left_p = ecma_get_extended_primitive_from_value (left_value);
  ecma_extended_primitive_t *right_p = ecma_get_extended_primitive_from_value (right_value);

  if (!(left_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN))
  {
    if (!(right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN))
    {
      return ecma_bigint_bitwise_op (ECMA_BIG_UINT_BITWISE_XOR, left_p, right_p);
    }

    /* x ^ (-y) == x ^ ~(y-1) == ~(x ^ (y-1)) == -((x ^ (y-1)) + 1) */
    uint32_t operation_and_options = (ECMA_BIG_UINT_BITWISE_XOR
                                      | ECMA_BIG_UINT_BITWISE_DECREASE_RIGHT
                                      | ECMA_BIG_UINT_BITWISE_INCREASE_RESULT);
    return ecma_bigint_bitwise_op (operation_and_options, left_p, right_p);
  }

  if (!(right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN))
  {
    /* (-x) | y == ~(x-1) ^ y == ~((x-1) ^ y) == -(((x-1) ^ y) + 1) */
    uint32_t operation_and_options = (ECMA_BIG_UINT_BITWISE_XOR
                                      | ECMA_BIG_UINT_BITWISE_DECREASE_LEFT
                                      | ECMA_BIG_UINT_BITWISE_INCREASE_RESULT);
    return ecma_bigint_bitwise_op (operation_and_options, left_p, right_p);
  }

  /* (-x) ^ (-y) == ~(x-1) ^ ~(y-1) == (x-1) ^ (y-1) */
  uint32_t operation_and_options = ECMA_BIG_UINT_BITWISE_XOR | ECMA_BIG_UINT_BITWISE_DECREASE_BOTH;
  return ecma_bigint_bitwise_op (operation_and_options, left_p, right_p);
} /* ecma_bigint_xor */

#endif /* JERRY_BUILTIN_BIGINT */
