diff --git a/src/arena.ts b/src/arena.ts index 7e7a903..e88e2b7 100644 --- a/src/arena.ts +++ b/src/arena.ts @@ -1,29 +1,27 @@ // CSS Data Arena - Single contiguous ArrayBuffer for all AST nodes // -// Each node occupies 40 bytes with the following layout: +// Each node occupies 32 bytes with the following layout: // Offset | Size | Field // -------|------|------------- // 0 | 1 | type // 1 | 1 | flags -// 2 | 2 | (padding) -// 4 | 4 | startOffset -// 8 | 2 | length -// 10 | 2 | (padding) -// 12 | 2 | contentStartDelta (offset from startOffset, property name / at-rule name) -// 14 | 2 | contentLength -// 16 | 2 | valueStartDelta (offset from startOffset, declaration value / at-rule prelude) -// 18 | 2 | valueLength -// 20 | 4 | firstChild -// 24 | 4 | lastChild -// 28 | 4 | nextSibling -// 32 | 4 | startLine -// 36 | 2 | startColumn -// 38 | 2 | (padding) +// 2 | 2 | length +// 4 | 4 | firstChild +// 8 | 4 | nextSibling +// 12 | 4 | startOffset +// 16 | 2 | contentStartDelta (offset from startOffset, property name / at-rule name) +// 18 | 2 | valueStartDelta (offset from startOffset, declaration value / at-rule prelude) +// 20 | 2 | contentLength +// 22 | 2 | valueLength +// 24 | 4 | startLine +// 28 | 2 | startColumn +// 30 | 1 | attr_operator (reusing padding) +// 31 | 1 | attr_flags (reusing padding) // // HOW THE ARENA WORKS: -// 1. BYTES_PER_NODE defines the size of each node (40 bytes). The ArrayBuffer size is calculated -// as: capacity × BYTES_PER_NODE. For example, 1024 nodes = 40,960 bytes (~40KB). -// Node indices map to byte offsets via: node_offset = node_index × 40. +// 1. BYTES_PER_NODE defines the size of each node (32 bytes). The ArrayBuffer size is calculated +// as: capacity × BYTES_PER_NODE. For example, 1024 nodes = 32,768 bytes (32KB). +// Node indices map to byte offsets via: node_offset = node_index × 32. // // 2. We use a single DataView over the ArrayBuffer to read/write different types at specific offsets. // - Uint8: 1-byte reads/writes for type, flags (e.g., view.getUint8(offset)) @@ -31,20 +29,21 @@ // - Uint32: 4-byte reads/writes for startOffset, pointers, line (e.g., view.getUint32(offset, true)) // The 'true' parameter specifies little-endian byte order (native on x86/ARM CPUs). // -// 3. Padding (6 bytes total at offsets 2-3, 10-11, 38-39) ensures memory alignment for performance: -// - Uint32 fields align to 4-byte boundaries (offsets 4, 20, 24, 28, 32) -// - Uint16 fields align to 2-byte boundaries (offsets 8, 10, 12, 14, 16, 18, 36, 38) +// 3. Padding (2 bytes total at offsets 30-31) ensures memory alignment for performance: +// - Uint32 fields align to 4-byte boundaries (offsets 4, 8, 12, 24) +// - Uint16 fields align to 2-byte boundaries (offsets 2, 16, 18, 20, 22, 28) // Aligned access is faster (single CPU instruction) vs unaligned (multiple memory accesses). // Modern CPUs penalize unaligned reads/writes, making padding essential for performance. // -// 4. The padding at offset 2-3 is reused for attribute selector data (attr_operator, attr_flags), +// 4. The padding at offset 30-31 is reused for attribute selector data (attr_operator, attr_flags), // making efficient use of otherwise wasted bytes. This is a space optimization trick. // // 5. Delta offsets (contentStartDelta, valueStartDelta) save memory: instead of storing absolute -// positions as uint32 (4 bytes), we store relative offsets as uint16 (2 bytes). This reduced -// node size from 44→40 bytes (10% smaller), saving memory while maintaining performance. +// positions as uint32 (4 bytes), we store relative offsets as uint16 (2 bytes). Removing unused +// lastChild field saved another 4 bytes. This reduced node size from 44→40→36→32 bytes (27% +// smaller than original), saving memory while maintaining performance. -let BYTES_PER_NODE = 40 +let BYTES_PER_NODE = 32 // Node type constants export const STYLESHEET = 1 @@ -174,71 +173,66 @@ export class CSSDataArena { // Read start offset in source get_start_offset(node_index: number): number { - return this.view.getUint32(this.node_offset(node_index) + 4, true) + return this.view.getUint32(this.node_offset(node_index) + 12, true) } // Read length in source get_length(node_index: number): number { - return this.view.getUint16(this.node_offset(node_index) + 8, true) + return this.view.getUint16(this.node_offset(node_index) + 2, true) } // Read content start offset (stored as delta from startOffset) get_content_start(node_index: number): number { const startOffset = this.get_start_offset(node_index) - const delta = this.view.getUint16(this.node_offset(node_index) + 12, true) + const delta = this.view.getUint16(this.node_offset(node_index) + 16, true) return startOffset + delta } // Read content length get_content_length(node_index: number): number { - return this.view.getUint16(this.node_offset(node_index) + 14, true) + return this.view.getUint16(this.node_offset(node_index) + 20, true) } // Read attribute operator (for NODE_SELECTOR_ATTRIBUTE) get_attr_operator(node_index: number): number { - return this.view.getUint8(this.node_offset(node_index) + 2) + return this.view.getUint8(this.node_offset(node_index) + 30) } // Read attribute flags (for NODE_SELECTOR_ATTRIBUTE) get_attr_flags(node_index: number): number { - return this.view.getUint8(this.node_offset(node_index) + 3) + return this.view.getUint8(this.node_offset(node_index) + 31) } // Read first child index (0 = no children) get_first_child(node_index: number): number { - return this.view.getUint32(this.node_offset(node_index) + 20, true) - } - - // Read last child index (0 = no children) - get_last_child(node_index: number): number { - return this.view.getUint32(this.node_offset(node_index) + 24, true) + return this.view.getUint32(this.node_offset(node_index) + 4, true) } // Read next sibling index (0 = no sibling) get_next_sibling(node_index: number): number { - return this.view.getUint32(this.node_offset(node_index) + 28, true) + return this.view.getUint32(this.node_offset(node_index) + 8, true) } // Read start line get_start_line(node_index: number): number { - return this.view.getUint32(this.node_offset(node_index) + 32, true) + return this.view.getUint32(this.node_offset(node_index) + 24, true) } // Read start column get_start_column(node_index: number): number { - return this.view.getUint16(this.node_offset(node_index) + 36, true) + return this.view.getUint16(this.node_offset(node_index) + 28, true) } // Read value start offset (stored as delta from startOffset, declaration value / at-rule prelude) get_value_start(node_index: number): number { const startOffset = this.get_start_offset(node_index) - const delta = this.view.getUint16(this.node_offset(node_index) + 16, true) + const delta = this.view.getUint16(this.node_offset(node_index) + 18, true) return startOffset + delta } // Read value length get_value_length(node_index: number): number { - return this.view.getUint16(this.node_offset(node_index) + 18, true) + return this.view.getUint16(this.node_offset(node_index) + 22, true) } // --- Write Methods --- @@ -255,67 +249,62 @@ export class CSSDataArena { // Write start offset in source set_start_offset(node_index: number, offset: number): void { - this.view.setUint32(this.node_offset(node_index) + 4, offset, true) + this.view.setUint32(this.node_offset(node_index) + 12, offset, true) } // Write length in source set_length(node_index: number, length: number): void { - this.view.setUint16(this.node_offset(node_index) + 8, length, true) + this.view.setUint16(this.node_offset(node_index) + 2, length, true) } // Write content start delta (offset from startOffset) set_content_start_delta(node_index: number, delta: number): void { - this.view.setUint16(this.node_offset(node_index) + 12, delta, true) + this.view.setUint16(this.node_offset(node_index) + 16, delta, true) } // Write content length set_content_length(node_index: number, length: number): void { - this.view.setUint16(this.node_offset(node_index) + 14, length, true) + this.view.setUint16(this.node_offset(node_index) + 20, length, true) } // Write attribute operator (for NODE_SELECTOR_ATTRIBUTE) set_attr_operator(node_index: number, operator: number): void { - this.view.setUint8(this.node_offset(node_index) + 2, operator) + this.view.setUint8(this.node_offset(node_index) + 30, operator) } // Write attribute flags (for NODE_SELECTOR_ATTRIBUTE) set_attr_flags(node_index: number, flags: number): void { - this.view.setUint8(this.node_offset(node_index) + 3, flags) + this.view.setUint8(this.node_offset(node_index) + 31, flags) } // Write first child index set_first_child(node_index: number, childIndex: number): void { - this.view.setUint32(this.node_offset(node_index) + 20, childIndex, true) - } - - // Write last child index - set_last_child(node_index: number, childIndex: number): void { - this.view.setUint32(this.node_offset(node_index) + 24, childIndex, true) + this.view.setUint32(this.node_offset(node_index) + 4, childIndex, true) } // Write next sibling index set_next_sibling(node_index: number, siblingIndex: number): void { - this.view.setUint32(this.node_offset(node_index) + 28, siblingIndex, true) + this.view.setUint32(this.node_offset(node_index) + 8, siblingIndex, true) } // Write start line set_start_line(node_index: number, line: number): void { - this.view.setUint32(this.node_offset(node_index) + 32, line, true) + this.view.setUint32(this.node_offset(node_index) + 24, line, true) } // Write start column set_start_column(node_index: number, column: number): void { - this.view.setUint16(this.node_offset(node_index) + 36, column, true) + this.view.setUint16(this.node_offset(node_index) + 28, column, true) } // Write value start delta (offset from startOffset, declaration value / at-rule prelude) set_value_start_delta(node_index: number, delta: number): void { - this.view.setUint16(this.node_offset(node_index) + 16, delta, true) + this.view.setUint16(this.node_offset(node_index) + 18, delta, true) } // Write value length set_value_length(node_index: number, length: number): void { - this.view.setUint16(this.node_offset(node_index) + 18, length, true) + this.view.setUint16(this.node_offset(node_index) + 22, length, true) } // --- Node Creation --- @@ -351,10 +340,10 @@ export class CSSDataArena { const offset = node_index * BYTES_PER_NODE this.view.setUint8(offset, type) // +0: type - this.view.setUint32(offset + 4, start_offset, true) // +4: startOffset - this.view.setUint16(offset + 8, length, true) // +8: length - this.view.setUint32(offset + 32, start_line, true) // +32: startLine - this.view.setUint16(offset + 36, start_column, true) // +36: startColumn + this.view.setUint16(offset + 2, length, true) // +2: length + this.view.setUint32(offset + 12, start_offset, true) // +12: startOffset + this.view.setUint32(offset + 24, start_line, true) // +24: startLine + this.view.setUint16(offset + 28, start_column, true) // +28: startColumn return node_index } @@ -367,8 +356,7 @@ export class CSSDataArena { if (children.length === 0) return const offset = this.node_offset(parent_index) - this.view.setUint32(offset + 20, children[0], true) // firstChild - this.view.setUint32(offset + 24, children[children.length - 1], true) // lastChild + this.view.setUint32(offset + 4, children[0], true) // firstChild // Chain siblings for (let i = 0; i < children.length - 1; i++) { diff --git a/src/parse-selector.ts b/src/parse-selector.ts index afc47f8..863a899 100644 --- a/src/parse-selector.ts +++ b/src/parse-selector.ts @@ -125,7 +125,6 @@ export class SelectorParser { // Set the complex selector chain as children this.arena.set_first_child(selector_wrapper, complex_selector) - this.arena.set_last_child(selector_wrapper, last_component) selectors.push(selector_wrapper) } @@ -748,7 +747,6 @@ export class SelectorParser { let child = this.parse_nth_expression(content_start, content_end) if (child !== null) { this.arena.set_first_child(node, child) - this.arena.set_last_child(node, child) } } else if (str_equals('lang', func_name_substr)) { // Parse as :lang() - comma-separated language identifiers @@ -771,7 +769,6 @@ export class SelectorParser { // Add as child if parsed successfully if (child_selector !== null) { this.arena.set_first_child(node, child_selector) - this.arena.set_last_child(node, child_selector) } } } @@ -847,7 +844,6 @@ export class SelectorParser { this.arena.set_first_child(parent_node, first_child) } if (last_child !== null) { - this.arena.set_last_child(parent_node, last_child) } // Restore lexer state @@ -897,11 +893,9 @@ export class SelectorParser { // Link An+B and selector list if (anplusb_node !== null && selector_list !== null) { this.arena.set_first_child(of_node, anplusb_node) - this.arena.set_last_child(of_node, selector_list) this.arena.set_next_sibling(anplusb_node, selector_list) } else if (anplusb_node !== null) { this.arena.set_first_child(of_node, anplusb_node) - this.arena.set_last_child(of_node, anplusb_node) } return of_node