mirror of
https://github.com/php/php-src.git
synced 2024-09-21 09:57:23 +00:00
Faster validation logic in bc_str2num() (#14115)
Using SIMD to accelerate the validation. Using the benchmark from #14076. After: ``` 1.3504369258881 1.6206321716309 1.6845638751984 ``` Before: ``` 1.4750170707703 1.9039781093597 1.9632289409637 ```
This commit is contained in:
parent
039344cf70
commit
cad0e555ac
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
@ -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') {
|
||||
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user