Zoltan Herczeg df2f7782f7
Implement BigInt primitve type and some of its operations (#4062)
Supported operations:
- parse BigInt (decimal, hexadecimal, binary)
- toString with any radix between 2 and 36
- arithmetic operations: negate, add, subtract, multiply, divide, modulo
- left and right shift

JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg.u-szeged@partner.samsung.com
2020-07-30 12:08:34 +02:00

509 lines
14 KiB
C

/* 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 "lit-char-helpers.h"
#if ENABLED (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 */
/**
* Parse a string 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 (const lit_utf8_byte_t *string_p, /**< string represenation of the BigInt */
lit_utf8_size_t size) /**< string size */
{
ecma_bigint_digit_t radix = 10;
uint32_t sign = 0;
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_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_raise_syntax_error (ECMA_ERR_MSG ("BigInt cannot be constructed from empty string"));
}
const lit_utf8_byte_t *string_end_p = string_p + size;
while (string_p < string_end_p && *string_p == LIT_CHAR_0)
{
string_p++;
}
ecma_extended_primitive_t *result_p = NULL;
if (string_p == string_end_p)
{
result_p = ecma_bigint_create (0);
}
else
{
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
{
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));
}
}
if (digit >= radix)
{
if (result_p != NULL)
{
ecma_deref_bigint (result_p);
}
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))
{
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 */
/**
* 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));
ecma_extended_primitive_t *bigint_p = ecma_get_extended_primitive_from_value (value);
if (ECMA_BIGINT_GET_SIZE (bigint_p) == 0)
{
return ecma_new_ecma_string_from_code_unit (LIT_CHAR_0);
}
uint32_t char_start_p, char_size_p;
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 */
/**
* 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 */
/**
* 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));
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 (right_size == 0)
{
ecma_ref_extended_primitive (left_p);
return left_value;
}
if (left_size == 0)
{
if (!is_add)
{
return ecma_bigint_negate (right_p);
}
ecma_ref_extended_primitive (right_p);
return right_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)
{
sign = 0;
result_p = ecma_bigint_create (0);
}
else 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));
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 == 0)
{
ecma_ref_extended_primitive (left_p);
return left_value;
}
if (right_size == 0)
{
ecma_ref_extended_primitive (right_p);
return right_value;
}
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));
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 (right_size == 0)
{
return ecma_raise_range_error (ECMA_ERR_MSG ("BigInt division by zero"));
}
if (left_size == 0)
{
ecma_ref_extended_primitive (left_p);
return left_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)
{
ecma_ref_extended_primitive (left_p);
return left_value;
}
else
{
result_p = ecma_bigint_create (0);
}
}
else if (compare_result == 0)
{
if (is_mod)
{
result_p = ecma_bigint_create (0);
}
else
{
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 (JERRY_UNLIKELY (result_p == NULL))
{
return ecma_bigint_raise_memory_error ();
}
if (ECMA_BIGINT_GET_SIZE (result_p) == 0)
{
return ecma_make_extended_primitive_value (result_p, ECMA_TYPE_BIGINT);
}
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));
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 right_size = ECMA_BIGINT_GET_SIZE (right_p);
if (right_size == 0 || ECMA_BIGINT_GET_SIZE (left_p) == 0)
{
ecma_ref_extended_primitive (left_p);
return left_value;
}
if (right_p->u.bigint_sign_and_size & ECMA_BIGINT_SIGN)
{
is_left = !is_left;
}
if (right_size > sizeof (ecma_bigint_digit_t))
{
if (is_left)
{
return ecma_bigint_raise_memory_error ();
}
return ecma_make_extended_primitive_value (ecma_bigint_create (0), ECMA_TYPE_BIGINT);
}
ecma_extended_primitive_t *result_p;
ecma_bigint_digit_t shift = ECMA_BIGINT_GET_LAST_DIGIT (right_p, sizeof (ecma_bigint_digit_t));
if (is_left)
{
result_p = ecma_big_uint_shift_left (left_p, shift);
}
else
{
result_p = ecma_big_uint_shift_right (left_p, shift);
}
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);
} /* ecma_bigint_shift */
#endif /* ENABLED (JERRY_BUILTIN_BIGINT) */