Skip to content

perf: inline const number[][] / number[] literal arrays as flat i32/f64 tables #50

Description

@proggeramlug

Context

Companion to #49. Still analyzing the image_conv hot loop post-#47/#48. The remaining bl 0x10001efa0 inside the inner kernel loop is js_value_to_double — called because KERNEL[ky+2][kx+2] returns a NaN-boxed JSValue (double-tagged), which then has to be coerced for the src[idx] * k multiply.

The pattern

const KERNEL: number[][] = [
  [1, 4, 7, 4, 1],
  [4, 16, 26, 16, 4],
  [7, 26, 41, 26, 7],
  [4, 16, 26, 16, 4],
  [1, 4, 7, 4, 1],
];
const KSUM = 273;

KERNEL is const, assigned once from a literal, never written. Elements are all int32-representable (max = 41). Access pattern in the hot loop:

const krow = KERNEL[ky + 2];
const k = krow[kx + 2];
rAcc += src[idx] * k;

Currently each krow[kx+2] load goes through the generic Array.prototype.get path, which reads a NaN-boxed double from arena storage and then js_value_to_double has to unwrap it.

Proposal

For module-level const X: number[][] = [literal] (or number[], string constants, etc.) with no writers in the whole program:

  1. Compile-time fold the literal into a static flat table in .rodata / .data.rel.ro.
  2. Detect the element type. If every element is int32-representable, emit the table as [N x i32]. Otherwise [N x double].
  3. Replace X[i][j] lookups with direct pointer arithmetic against the static table (after bounds check if the indices aren't compile-time-known).

For KERNEL[ky+2][kx+2] the emitted code becomes:

; KERNEL_FLAT = [1,4,7,4,1, 4,16,26,16,4, 7,26,41,26,7, ...] as [25 x i32]
add   w_offset, w_ky, #2
mov   w_row, #5
mul   w_offset, w_offset, w_row
add   w_offset, w_offset, w_kx
add   w_offset, w_offset, #2
ldr   w_k, [X_KERNEL_FLAT, w_offset, uxtw #2]

— no function call, no NaN-box unwrap, no arena pointer chase. Pairs with #49 to keep rAcc as i32 end-to-end.

Impact

Scope notes

  • Only for const bindings that no code path reassigns, modifies (push/splice/bracket-set), or reflectively probes. If in doubt, bail.
  • Strings inside const object-literals could get the same treatment (inline interned).
  • Works even for let if escape analysis proves the reference never leaves the declaring function — but const is the 99% case.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions