diff --git a/src/constant_pool.c b/src/constant_pool.c index 90201ebb8e..6e9c5c6eba 100644 --- a/src/constant_pool.c +++ b/src/constant_pool.c @@ -245,7 +245,9 @@ pm_constant_pool_find(const pm_constant_pool_t *pool, const uint8_t *start, size pm_constant_pool_bucket_t *bucket; while (bucket = &pool->buckets[index], bucket->id != PM_CONSTANT_ID_UNSET) { - if ((bucket->length == length) && memcmp(bucket->start, start, length) == 0) { + // Compare the stored hash before touching the contents so that probe + // collisions are rejected without a memcmp call. + if ((bucket->hash == hash) && (bucket->length == length) && memcmp(bucket->start, start, length) == 0) { return bucket->id; } @@ -274,8 +276,9 @@ pm_constant_pool_insert(pm_arena_t *arena, pm_constant_pool_t *pool, const uint8 while (bucket = &pool->buckets[index], bucket->id != PM_CONSTANT_ID_UNSET) { // If there is a collision, then we need to check if the content is the // same as the content we are trying to insert. If it is, then we can - // return the id of the existing constant. - if ((bucket->length == length) && memcmp(bucket->start, start, length) == 0) { + // return the id of the existing constant. Compare the stored hash + // first so that probe collisions are rejected without a memcmp call. + if ((bucket->hash == hash) && (bucket->length == length) && memcmp(bucket->start, start, length) == 0) { // Since we have found a match, we need to check if this is // attempting to insert a shared or an owned constant. We want to // prefer shared constants since they don't require allocations. diff --git a/src/prism.c b/src/prism.c index 511223aaee..d1355fe53b 100644 --- a/src/prism.c +++ b/src/prism.c @@ -8716,6 +8716,13 @@ lex_identifier(pm_parser_t *parser, bool previous_command_start) { if (encoding_changed) { return parser->encoding->isupper_char(current_start, end - current_start) ? PM_TOKEN_CONSTANT : PM_TOKEN_IDENTIFIER; } + + /* Identifiers usually start with an ASCII byte, for which the uppercase + * check is a simple range comparison. This avoids the call into the + * encoding module for every identifier. */ + if (*current_start < 0x80) { + return (*current_start >= 'A' && *current_start <= 'Z') ? PM_TOKEN_CONSTANT : PM_TOKEN_IDENTIFIER; + } return pm_encoding_utf_8_isupper_char(current_start, end - current_start) ? PM_TOKEN_CONSTANT : PM_TOKEN_IDENTIFIER; } @@ -10093,7 +10100,11 @@ parser_lex(pm_parser_t *parser) { // token. Skip runs of inline whitespace in bulk to avoid per-character // stores back to parser->current.end. bool chomping = true; - while (parser->current.end < parser->end && chomping) { + while (chomping) { + /* Skip the run of inline whitespace in bulk, then decide what + * to do based on the first byte after it. Handling both in a + * single pass avoids re-entering the scan when the run was + * non-empty, which is the common case. */ { static const uint8_t inline_whitespace[256] = { [' '] = 1, ['\t'] = 1, ['\f'] = 1, ['\v'] = 1 @@ -10103,8 +10114,8 @@ parser_lex(pm_parser_t *parser) { if (scan > parser->current.end) { parser->current.end = scan; space_seen = true; - continue; } + if (scan >= parser->end) break; } switch (*parser->current.end) {