Skip to content

Faster validation logic in bc_str2num() #14115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions ext/bcmath/bcmath.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@ PHP_MINFO_FUNCTION(bcmath)

/* {{{ php_str2num
Convert to bc_num detecting scale */
static zend_result php_str2num(bc_num *num, char *str)
static zend_result php_str2num(bc_num *num, const zend_string *str)
{
if (!bc_str2num(num, str, 0, true)) {
if (!bc_str2num(num, ZSTR_VAL(str), ZSTR_VAL(str) + ZSTR_LEN(str), 0, true)) {
return FAILURE;
}

Expand Down Expand Up @@ -167,12 +167,12 @@ PHP_FUNCTION(bcadd)
scale = (int) scale_param;
}

if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
if (php_str2num(&first, left) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}

if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
if (php_str2num(&second, right) == FAILURE) {
zend_argument_value_error(2, "is not well-formed");
goto cleanup;
}
Expand Down Expand Up @@ -214,12 +214,12 @@ PHP_FUNCTION(bcsub)
scale = (int) scale_param;
}

if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
if (php_str2num(&first, left) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}

if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
if (php_str2num(&second, right) == FAILURE) {
zend_argument_value_error(2, "is not well-formed");
goto cleanup;
}
Expand Down Expand Up @@ -261,12 +261,12 @@ PHP_FUNCTION(bcmul)
scale = (int) scale_param;
}

if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
if (php_str2num(&first, left) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}

if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
if (php_str2num(&second, right) == FAILURE) {
zend_argument_value_error(2, "is not well-formed");
goto cleanup;
}
Expand Down Expand Up @@ -310,12 +310,12 @@ PHP_FUNCTION(bcdiv)

bc_init_num(&result);

if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
if (php_str2num(&first, left) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}

if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
if (php_str2num(&second, right) == FAILURE) {
zend_argument_value_error(2, "is not well-formed");
goto cleanup;
}
Expand Down Expand Up @@ -362,12 +362,12 @@ PHP_FUNCTION(bcmod)

bc_init_num(&result);

if (php_str2num(&first, ZSTR_VAL(left)) == FAILURE) {
if (php_str2num(&first, left) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}

if (php_str2num(&second, ZSTR_VAL(right)) == FAILURE) {
if (php_str2num(&second, right) == FAILURE) {
zend_argument_value_error(2, "is not well-formed");
goto cleanup;
}
Expand Down Expand Up @@ -415,17 +415,17 @@ PHP_FUNCTION(bcpowmod)

bc_init_num(&result);

if (php_str2num(&bc_base, ZSTR_VAL(base_str)) == FAILURE) {
if (php_str2num(&bc_base, base_str) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}

if (php_str2num(&bc_expo, ZSTR_VAL(exponent_str)) == FAILURE) {
if (php_str2num(&bc_expo, exponent_str) == FAILURE) {
zend_argument_value_error(2, "is not well-formed");
goto cleanup;
}

if (php_str2num(&bc_modulus, ZSTR_VAL(modulus_str)) == FAILURE) {
if (php_str2num(&bc_modulus, modulus_str) == FAILURE) {
zend_argument_value_error(3, "is not well-formed");
goto cleanup;
}
Expand Down Expand Up @@ -489,12 +489,12 @@ PHP_FUNCTION(bcpow)

bc_init_num(&result);

if (php_str2num(&first, ZSTR_VAL(base_str)) == FAILURE) {
if (php_str2num(&first, base_str) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}

if (php_str2num(&bc_exponent, ZSTR_VAL(exponent_str)) == FAILURE) {
if (php_str2num(&bc_exponent, exponent_str) == FAILURE) {
zend_argument_value_error(2, "is not well-formed");
goto cleanup;
}
Expand Down Expand Up @@ -546,7 +546,7 @@ PHP_FUNCTION(bcsqrt)
scale = (int) scale_param;
}

if (php_str2num(&result, ZSTR_VAL(left)) == FAILURE) {
if (php_str2num(&result, left) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}
Expand Down Expand Up @@ -588,12 +588,12 @@ PHP_FUNCTION(bccomp)
scale = (int) scale_param;
}

if (!bc_str2num(&first, ZSTR_VAL(left), scale, false)) {
if (!bc_str2num(&first, ZSTR_VAL(left), ZSTR_VAL(left) + ZSTR_LEN(left), scale, false)) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}

if (!bc_str2num(&second, ZSTR_VAL(right), scale, false)) {
if (!bc_str2num(&second, ZSTR_VAL(right), ZSTR_VAL(right) + ZSTR_LEN(right), scale, false)) {
zend_argument_value_error(2, "is not well-formed");
goto cleanup;
}
Expand All @@ -617,7 +617,7 @@ static void bcfloor_or_bcceil(INTERNAL_FUNCTION_PARAMETERS, bool is_floor)
Z_PARAM_STR(numstr)
ZEND_PARSE_PARAMETERS_END();

if (php_str2num(&num, ZSTR_VAL(numstr)) == FAILURE) {
if (php_str2num(&num, numstr) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}
Expand Down Expand Up @@ -678,7 +678,7 @@ PHP_FUNCTION(bcround)

bc_init_num(&result);

if (php_str2num(&num, ZSTR_VAL(numstr)) == FAILURE) {
if (php_str2num(&num, numstr) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}
Expand Down
2 changes: 1 addition & 1 deletion ext/bcmath/libbcmath/src/bcmath.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ static inline bc_num bc_copy_num(bc_num num)

void bc_init_num(bc_num *num);

bool bc_str2num(bc_num *num, char *str, size_t scale, bool auto_scale);
bool bc_str2num(bc_num *num, const char *str, const char *end, size_t scale, bool auto_scale);

zend_string *bc_num2str_ex(bc_num num, size_t scale);

Expand Down
69 changes: 54 additions & 15 deletions ext/bcmath/libbcmath/src/str2num.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,55 @@
#include "convert.h"
#include <stdbool.h>
#include <stddef.h>
#ifdef __SSE2__
# include <emmintrin.h>
#endif

/* Convert strings to bc numbers. Base 10 only.*/
static const char *bc_count_digits(const char *str, const char *end)
{
/* Process in bulk */
#ifdef __SSE2__
const __m128i offset = _mm_set1_epi8((signed char) (SCHAR_MIN - '0'));
/* we use the less than comparator, so add 1 */
const __m128i threshold = _mm_set1_epi8(SCHAR_MIN + ('9' + 1 - '0'));

while (str + sizeof(__m128i) <= end) {
__m128i bytes = _mm_loadu_si128((const __m128i *) str);
/* Wrapping-add the offset to the bytes, such that all bytes below '0' are positive and others are negative.
* More specifically, '0' will be -128 and '9' will be -119. */
bytes = _mm_add_epi8(bytes, offset);
/* Now mark all bytes that are <= '9', i.e. <= -119, i.e. < -118, i.e. the threshold. */
bytes = _mm_cmplt_epi8(bytes, threshold);

int mask = _mm_movemask_epi8(bytes);
if (mask != 0xffff) {
/* At least one of the bytes is not within range. Move to the first offending byte. */
#ifdef PHP_HAVE_BUILTIN_CTZL
return str + __builtin_ctz(~mask);
#else
break;
#endif
}

str += sizeof(__m128i);
}
#endif

while (*str >= '0' && *str <= '9') {
str++;
}

return str;
}

/* Assumes `num` points to NULL, i.e. does yet not hold a number. */
bool bc_str2num(bc_num *num, char *str, size_t scale, bool auto_scale)
bool bc_str2num(bc_num *num, const char *str, const char *end, size_t scale, bool auto_scale)
{
size_t digits = 0;
size_t str_scale = 0;
char *ptr = str;
char *fractional_ptr = NULL;
char *fractional_end = NULL;
const char *ptr = str;
const char *fractional_ptr = NULL;
const char *fractional_end = NULL;
bool zero_int = false;

ZEND_ASSERT(*num == NULL);
Expand All @@ -59,12 +97,10 @@ bool bc_str2num(bc_num *num, char *str, size_t scale, bool auto_scale)
}
const char *integer_ptr = ptr;
/* digits before the decimal point */
while (*ptr >= '0' && *ptr <= '9') {
ptr++;
digits++;
}
ptr = bc_count_digits(ptr, end);
size_t digits = ptr - integer_ptr;
/* decimal point */
char *decimal_point = (*ptr == '.') ? ptr : NULL;
const char *decimal_point = (*ptr == '.') ? ptr : NULL;

/* If a non-digit and non-decimal-point indicator is in the string, i.e. an invalid character */
if (!decimal_point && *ptr != '\0') {
Expand All @@ -80,14 +116,17 @@ bool bc_str2num(bc_num *num, char *str, size_t scale, bool auto_scale)
}

/* validate */
while (*fractional_ptr >= '0' && *fractional_ptr <= '9') {
fractional_end = *fractional_ptr != '0' ? fractional_ptr + 1 : fractional_end;
fractional_ptr++;
}
if (*fractional_ptr != '\0') {
fractional_end = bc_count_digits(fractional_ptr, end);
if (*fractional_end != '\0') {
/* invalid num */
goto fail;
}

/* Exclude trailing zeros. */
while (fractional_end - 1 > decimal_point && fractional_end[-1] == '0') {
fractional_end--;
}

/* Move the pointer to the beginning of the fraction. */
fractional_ptr = decimal_point + 1;

Expand Down
Loading