perf(linter/plugins): use shift over multiply for powers of 2 (#27536)

Follow on after #27393.

After that PR, `Comment` is now 24 bytes, whereas `Token` is 16 bytes. In Oxlint plugins code, this change required using multiplication to convert from comment index to position in buffer. But `Token`'s size is still a power of 2, so can still use shift for that (faster).

Also, the shift required for these conversions for the tokens/comments/tokens-and-comments buffers were hardcoded in various places. Replace them all with constants - `<< 2` -> `<< TOKEN_SIZE32_SHIFT` - to make is clearer what's going on.
This commit is contained in:
overlookmotel committed 2026-10-10 11:19:59 +00:00
1 parent 0285738b2c
commit c5f9235e0a
5 files changed
+29 -12

No files matched your search

@@ -12,6 +12,8 @@ import {
initComments,
initCommentsBuffer,
} from "./comments.ts";
// oxlint-disable-next-line no-unused-vars -- used in inlined `firstTokenAtOrAfter`
import { TOKEN_SIZE32_SHIFT } from "./tokens.ts";
import {
initTokensAndCommentsBuffer,
tokensAndCommentsInt32,
+4 -2
View File
@@ -267,6 +267,8 @@ const TOKEN_DESERIALIZED_FLAG_OFFSET = TOKEN_SIZE - 1;
const TOKEN_SIZE_SHIFT = 4;
debugAssert(TOKEN_SIZE === 1 << TOKEN_SIZE_SHIFT);
export const TOKEN_SIZE32_SHIFT = TOKEN_SIZE_SHIFT - 2;
debugAssert(TOKEN_SIZE32 === 1 << TOKEN_SIZE32_SHIFT);
const KIND_FIELD_OFFSET = 8;
const IS_ESCAPED_FIELD_OFFSET = 10;
@@ -350,7 +352,7 @@ export function initTokensBuffer(): void {
const arrayBuffer = buffer.buffer,
absolutePos = buffer.byteOffset + tokensPos;
tokensUint8 = new Uint8Array(arrayBuffer, absolutePos, tokensLen << TOKEN_SIZE_SHIFT);
tokensInt32 = new Int32Array(arrayBuffer, absolutePos, tokensLen << (TOKEN_SIZE_SHIFT - 2));
tokensInt32 = new Int32Array(arrayBuffer, absolutePos, tokensLen << TOKEN_SIZE32_SHIFT);
// Grow caches if needed. After first few files, caches should have grown large enough to service all files.
// Later files will skip this step, and allocations stop.
@@ -514,7 +516,7 @@ function debugCheckValidRanges(): void {
let lastEnd = 0;
for (let i = 0; i < tokensLen; i++) {
const pos32 = i << 2;
const pos32 = i << TOKEN_SIZE32_SHIFT;
const start = tokensInt32![pos32];
const end = tokensInt32![pos32 + 1];
if (end <= start) throw new Error(`Invalid token range: ${start}-${end}`);
@@ -25,6 +25,7 @@ import {
tokensLen,
tokensInt32,
TOKEN_SIZE32,
TOKEN_SIZE32_SHIFT,
} from "./tokens.ts";
import { EMPTY_INT32_ARRAY } from "../utils/typed_arrays.ts";
import { debugAssert, debugAssertIsNonNull } from "../utils/asserts.ts";
@@ -196,7 +197,7 @@ function mergeTokensAndComments(tokensInt32: Int32Array, commentsInt32: Int32Arr
);
return;
}
tokenStart = tokensInt32[tokenIndex * TOKEN_SIZE32];
tokenStart = tokensInt32[tokenIndex << TOKEN_SIZE32_SHIFT];
} while (tokenStart < commentStart);
// Process run of comments
@@ -297,7 +298,7 @@ export function getTokenOrCommentEnd(entryIndex: number): number {
const pos32 = entryIndex << MERGED_SIZE32_SHIFT;
const originalIndex = tokensAndCommentsInt32![pos32 + MERGED_ORIGINAL_INDEX_OFFSET32];
return tokensAndCommentsInt32![pos32 + MERGED_TYPE_OFFSET32] === MERGED_TYPE_TOKEN
? tokensInt32![originalIndex * TOKEN_SIZE32 + 1]
? tokensInt32![(originalIndex << TOKEN_SIZE32_SHIFT) + 1]
: commentsInt32![originalIndex * COMMENT_SIZE32 + 1];
}
+17 -7
View File
@@ -2,13 +2,21 @@
* `SourceCode` methods related to tokens.
*/
import { cachedTokens, tokensInt32, tokensLen, initTokensBuffer, getToken } from "./tokens.ts";
import {
cachedTokens,
tokensInt32,
tokensLen,
initTokensBuffer,
getToken,
TOKEN_SIZE32_SHIFT,
} from "./tokens.ts";
import {
tokensAndCommentsInt32,
tokensAndCommentsLen,
getTokenOrComment,
getTokenOrCommentEnd,
initTokensAndCommentsBuffer,
MERGED_SIZE32_SHIFT,
} from "./tokens_and_comments.ts";
import { debugAssertIsNonNull } from "../utils/asserts.ts";
@@ -1290,7 +1298,7 @@ export function getTokenByRangeStart<Options extends RangeOptions | null | undef
// This makes it safe to use `>> 1` for division by 2 (which is faster than `>>> 1`).
for (let lo = 0, hi = len; lo < hi;) {
const mid = (lo + hi) >> 1;
const tokenStart = int32[mid << 2];
const tokenStart = int32[mid << TOKEN_SIZE32_SHIFT];
if (tokenStart < offset) {
lo = mid + 1;
} else if (tokenStart > offset) {
@@ -1357,7 +1365,7 @@ export function isSpaceBetween(first: NodeOrToken, second: NodeOrToken): boolean
let index = firstTokenAtOrAfter(tokensAndCommentsInt32, rangeStart, 0, tokensAndCommentsLen);
for (let lastTokenEnd = rangeStart; index < tokensAndCommentsLen; index++) {
const tokenStart = tokensAndCommentsInt32[index << 2];
const tokenStart = tokensAndCommentsInt32[index << MERGED_SIZE32_SHIFT];
// The first token of the later node should undergo the check in the second branch
if (tokenStart > rangeEnd) break;
if (tokenStart !== lastTokenEnd) return true;
@@ -1413,7 +1421,7 @@ export function isSpaceBetweenTokens(first: NodeOrToken, second: NodeOrToken): b
let index = firstTokenAtOrAfter(tokensAndCommentsInt32, rangeStart, 0, tokensAndCommentsLen);
for (let lastTokenEnd = rangeStart; index < tokensAndCommentsLen; index++) {
const tokenStart = tokensAndCommentsInt32[index << 2];
const tokenStart = tokensAndCommentsInt32[index << MERGED_SIZE32_SHIFT];
// The first token of the later node should undergo the check in the second branch
if (tokenStart > rangeEnd) break;
@@ -1471,7 +1479,7 @@ function getEntry(index: number, includeComments: boolean): TokenOrComment {
* @returns Start offset in source text
*/
function entryStart(index: number, int32: Int32Array): number {
return int32[index << 2];
return int32[index << TOKEN_SIZE32_SHIFT];
}
/**
@@ -1483,7 +1491,9 @@ function entryStart(index: number, int32: Int32Array): number {
* @returns End offset in source text
*/
function entryEnd(index: number, includeComments: boolean): number {
return includeComments === true ? getTokenOrCommentEnd(index) : tokensInt32![(index << 2) + 1];
return includeComments === true
? getTokenOrCommentEnd(index)
: tokensInt32![(index << TOKEN_SIZE32_SHIFT) + 1];
}
/**
@@ -1550,7 +1560,7 @@ export function firstTokenAtOrAfter(
): number {
for (let endIndex = length; startIndex < endIndex;) {
const mid = (startIndex + endIndex) >> 1;
if (int32[mid << 2] < offset) {
if (int32[mid << TOKEN_SIZE32_SHIFT] < offset) {
startIndex = mid + 1;
} else {
endIndex = mid;
+3 -1
View File
@@ -30,6 +30,8 @@ const { fnParams, returnParamIndex, fnBodySource } = extractInlinedFunction(
*
* The function is inlined into all call sites in `FILES` list above.
*
* `TOKEN_SIZE32_SHIFT` constant must be in scope at all call sites.
*
* ```ts
* // Original code
* const index = firstTokenAtOrAfter(int32, rangeStart, searchFromIndex, length);
@@ -38,7 +40,7 @@ const { fnParams, returnParamIndex, fnBodySource } = extractInlinedFunction(
* let index = searchFromIndex;
* for (let endIndex = length; index < endIndex; ) {
* const mid = (index + endIndex) >> 1;
* if (int32[mid << 2] < rangeStart) {
* if (int32[mid << TOKEN_SIZE32_SHIFT] < rangeStart) {
* index = mid + 1;
* } else {
* endIndex = mid;