Commit Graph
46 Commits
Author SHA1 Message Date
Chris Tate c820a6c57f Compute products of typed-array reads in 32 bits under bitwise operators
A sum of products of integer typed-array reads that only reaches a
bitwise operator, such as `(p[j] * 77 + p[j + 1] * 150) >> 8`, now adds
in 32-bit integers like a sum of the reads themselves: a product whose
every result is a safe integer, and never -0, is an exact integer or NaN.
A NaN-coded read passes its validity bit up through those sums, so the
integer result selects 0 for a missed read without a floating compare,
and a loop over such sums vectorizes without converting to doubles.
2026-10-25 20:29:18 -05:00
Chris Tate ffc7af36c7 Read a typed array's length without an invariant header load
`length` and `byteLength` loaded the typed array's header with
`!invariant.load` (#2016). LLVM may move such a load down to its use,
past the release that frees the view, so `const slots = this.slots;
this.slots = next; return slots.length` returned a garbage number in
release builds. These reads are now ordinary loads that keep the
header's alias tag. Header loads that only bound or address an element
access stay invariant: their values reach only that access, which runs
while the view is alive, so typed-array loops still hoist the length
and data pointer.

Corpus typed-array-length-after-release covers a replaced field, a
deleted map entry and a rebound global.
2026-10-25 09:59:43 -05:00
Chris Tate 4e9a4311b0 Prove a typed-array loop's views disjoint and give each its own alias scope
A loop that reads one typed array and writes another kept every element
in memory: separate views can share a buffer, so LLVM had to assume a
store to one could change the other's elements. A loop of eight lanes
held in Float64Arrays (zr[k], zi[k], tr[k], ti[k]) ran as scalar loads
and stores of each lane on every step.

When a typed-array loop version covers at least two views and writes at
least one of them, its guard now also requires every pair of the views'
byte ranges to be disjoint: one ends at or before the other starts. In
the fast copy, each view's element reads and writes carry an alias scope
of their own and no-alias with the others' scopes, so LLVM keeps the
elements in registers and vectorizes across lanes. Views that share
bytes, or one array passed twice, take the checked copy as before. The
scopes are string-named, written inline, and named after the function
and the version, so every copy of a loop has its own. Worker programs
scope only views the guard proves thread-owned.

Corpus 8182 runs such loops over disjoint, adjacent, overlapping and
identical views, views of different element types over one buffer, and
empty views.
2026-10-24 21:15:01 -05:00
Chris Tate 1af3a86370 Compile unions of typed-array classes (#2011) 2026-10-23 20:00:12 -05:00
Chris Tate 7610edcfa0 Compare and index with the integer beside a sum's double
A sum or product of exact integers whose result range is not proven, such
as `child + 1` of a speculated loop counter, computes its double and, beside
it, an i64 wide view (`wideArithmetic`). Array indexes already used that
view, but number compares and typed-array indexes did not:
`child + 1 < end` compared doubles, and `a[child + 1]` converted the double
back to an integer and tested the round trip before its bounds compare.

`alwaysExactInteger` gives a value's exact integer, or its wide view when
the view's magnitude bound is within the magnitude below which the view
equals the double. A number compare whose two operands both have one
compares the i64s, and a typed-array index uses it directly. Views that
may differ from their double, such as `2 * root + 1` of a counter that may
reach 2^53, keep the double compare.
2026-10-23 16:16:43 -05:00
Chris Tate e901526c8b Point the renamed order-parity baselines at their corpus programs 2026-10-23 11:35:14 -05:00
Chris Tate f3bb5d2cb1 Borrow the array of an inline Atomics operation from its local
emitInlineAtomicsOp read its typed array with emitExpr, so every Atomics
call on a local array retained the array before the atomic instruction and
released it after, two out-of-line calls around one instruction. The
array now goes through emitBorrowedInput: an unchanged, unboxed local owns
it across the later operands and the operation, and the instruction and
scr_atomics_op only read it. Fields, globals, captures and bindings an
operand can reassign keep their own reference.
2026-10-22 20:57:07 -05:00
Chris Tate 81bf6cb0c7 Check integer-or-NaN indexes without a double round trip
An array or typed-array index that is an exact integer or NaN, such as an
element read that may miss (`t[i]!`), a `charCodeAt`, or a sum of those, is
a valid index when it is ordered and below the length. The index check now
tests that with one ordered compare and the integer, taken from the value's
32-bit view or from one conversion, instead of converting the index back to a
double and comparing it.

Number locals that are declared once with an initializer, are not captured
or boxed, and are only written such values carry the same fact, so
`const c = t[i]!; table[c]` and `(h * 31 + c) | 0` take the integer paths
that the inline read already takes.
2026-10-22 17:14:28 -05:00
Chris Tate 159740c685 Convert doubles below 2^63 to int32 with one conversion
ToInt32 and ToUint32 of a double that no range proves an integer took the
i64 conversion only up to 2^53 in magnitude, and decoded the double's bits
above that. Every double between 2^53 and 2^63 is an integer, so the i64
conversion is exact there too, and its low 32 bits are the result. The bit
decoding now handles only NaN, the infinities and magnitudes from 2^63.

Multiplicative hashes such as `((h ^ b) * 16777619) >>> 0` and
`(h * 1000003 + b) | 0`, whose products usually pass 2^53, convert on the
fast path.
2026-10-22 16:57:09 -05:00
Chris Tate 491480e1ad Convert clamped stores inline and test thread-owned views once in typed-array loops
A Uint8ClampedArray store called scr_bytes_to_u8_clamp for every element,
which kept loops of clamped stores scalar. The store now computes
ToUint8Clamp in line: NaN and values at most 0 store 0, values at least
255 store 255, and values between round through (v + 2^52) - 2^52, which
rounds a value in (0, 255) to the nearest integer, ties to even, as the
runtime function does.

In worker-enabled programs, every typed-array element access tests the
view's shared storage and branches between a relaxed atomic access and
the ordinary one, so loops over thread-owned views kept one branch per
access. A view's shared storage is fixed at construction, like its length,
so the typed-array loop version guard now tests it once for each invariant
receiver, and the fast copy reads and writes those views directly. In
worker programs a loop is versioned for this even when no bound needs a
guard. When any receiver is shared, the checked copy runs unchanged.
Programs without workers emit the same code as before.

Adds a Uint8ClampedArray brighten kernel to the typed-array-kernels
workload.
2026-10-22 02:09:43 -05:00
Chris Tate 8bbccee4f6 Read and write shared typed-array elements inline 2026-10-22 00:37:37 -05:00
Malte Ubl de782764c2 Version typed-array loops on one bounds guard and tag typed-array memory for alias analysis
Typedarray element stores could alias every header and module-global load,
so each access reloaded the length, data pointer and global receiver slot and
kept its own bounds branch. Element accesses now share one TBAA category
(views of any element type may share a buffer), header loads another, and
whole-slot loads of bytes/number globals their own, so LICM keeps them in
registers. The data pointer loads beside the length, ahead of the branch.

ScrBytes lengths and data pointers are fixed at construction, so innermost
synchronous for loops whose accesses use a statically ranged index or an
affine counter index (k * counter + invariant + c) are versioned: one test
before the loop proves every access in bounds (bounding the counter so every
affine operand stays an exact safe integer), and the copy behind it runs
without per-element checks. The checked loop keeps exact out-of-bounds,
NaN, fractional and DataView-throw behavior.
2026-10-18 07:32:55 -07:00
Chris Tate 32061e8ba3 Add Float16Array
Float16Array is a native typed-array kind with IEEE 754 binary16 storage.
Stores round the number to nearest-even in one step from the double, as
Node does, including overflow to Infinity, subnormals, -0 and NaN; reads
widen exactly. The runtime converts with integer operations, so no target
needs half-precision hardware or compiler-rt support.

Construction, element reads and writes, ArrayBuffer views, fill, set,
subarray, slice, both sort orders, constructor values and checked-dynamic
storage work as for the other typed arrays. Math.f16round,
DataView.prototype.getFloat16 and setFloat16, and util.types.isFloat16Array
compile.
2026-10-17 22:45:32 -05:00
Malte UblandClaude Opus 5.5 24c54fc5d3 Add Int32[] element contracts
`Int32` from @scriptc/types now also annotates the element type of a
declared number array (`Int32[]`, `readonly Int32[]`, `Array<Int32>`,
`ReadonlyArray<Int32>`) on parameters, variables, class instance fields
and function results. The contract promises that every element stored
into the array, through any reference, is an int32 by construction.

contracts/int32-elements.ts checks it over a whole-program flow graph of
the places array references live (locals, parameters, globals, results,
class field families, record fields, container element slots), with one
node per allocation and per opaque origin. Allocations that can reach an
annotated slot are constrained and every element store into an array that
may be constrained must be range-proven an int32 (SC7002); element copies
constrain their sources by fixpoint; arrays from or escaping to code the
IR does not show refuse the contract (SC7003). Slots only constrained
arrays reach are recorded as IrContracts.int32Elements.

Element reads that find a value through those slots carry the int32
range, `getNumber` reads are integer-or-NaN operands of wrapping sums, and
ToInt32 of any integer-or-NaN operand is branch-free. Holes, missing
indices and length changes keep their JavaScript behavior.

Integer kernels over a 2^20-element array: `(s + a[i]) | 0` 1086 -> 175 ms,
`(Math.imul(h, 31) + a[i]) | 0` 602 -> 150 ms, prefix sums 575 -> 155 ms;
programs without the annotation compile to identical binaries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:08:06 -07:00
Chris Tate 0d59abe143 Pool small Buffers as Node does
Buffer.from of strings, arrays and Uint8Arrays, Buffer.allocUnsafe and
Buffer.concat now return views into a per-thread pool slab of
Buffer.poolSize bytes (64 KiB by default) when the size is below
Buffer.poolSize >>> 1, following Node's allocate, fromStringFast and
fromArrayLike: 8-byte aligned offsets, a new slab when a request does not
fit, and the four-bytes-per-unit reservation for strings. Buffer.poolSize
can be read and set, and Buffer.allocUnsafeSlow returns an unpooled Buffer.
A slab lives while any of its views does. Its ArrayBuffer cannot be
transferred, and structuredClone of a typed array view copies the whole
ArrayBuffer and keeps the view's offset. Buffer.alloc, allocUnsafe and
allocUnsafeSlow validate sizes with Node's ERR_OUT_OF_RANGE error.

The first pooled allocation finds the startup slab, which has the
default 64 KiB size. A request it cannot hold gets a slab of the current
Buffer.poolSize, as in Node, and the room is checked after every new
slab, so a Buffer.poolSize raised before the first pooled allocation
cannot place a reservation past the end of a 64 KiB slab. A reservation
larger than a fresh slab traps instead of writing past it.

Corpus 5531 (typed) and 5532 (untyped) raise Buffer.poolSize before the
first pooled allocation and allocate just under the new threshold
through each pooled path, each case in a fresh child process, and also
shrink Buffer.poolSize and change it between allocations. The
node-types fenced fixture no longer lists Buffer.poolSize, which now
compiles.
2026-10-16 01:39:10 -05:00
Chris Tate 93bc902cd8 Emit Atomics operations inline as atomic instructions
Atomics.load, store, exchange, compareExchange, add, sub, and, or and xor
on an integer typed array compile to one sequentially consistent LLVM
atomic instruction on the element. An index that is not an integer in
range takes the runtime operation, which applies ToIndex and raises its
errors.
2026-10-16 00:31:52 -05:00
Chris Tate d4a51ce968 Keep typed-array indices integral through the shared-buffer check
In worker-enabled executables a typed-array access tests whether the view
is shared. The test used to come before the index check, and the shared
path called scr_bytes_get_or_nan/scr_bytes_set with the index as a
double, then converted the element back to an integer. Both paths now
use the index the inline check produced: the shared test follows the
check (a view's length never changes), and the shared path calls new
runtime entries that take the index as a size_t
(scr_bytes_shared_get_int, scr_bytes_shared_get_f64,
scr_bytes_shared_set). Integer elements join as one 32-bit value and
convert to a number after the join, so compares and ToInt32/ToUint32
of the result stay integer operations in every element kind.

The amortized worker cancellation poll moves from the loop header to
the loop's back edge (the continue target). In the header it was a loop
exit ahead of the body, which kept LLVM from rotating the loop and left
loads such as the view's length inside it.

Adds the typed-array-scan and typed-array-scan-workers runtime
benchmarks: Uint8Array, Uint16Array, Int32Array and Float64Array scan
loops in an ordinary and a worker-enabled executable.
2026-10-15 14:19:47 -05:00
Chris Tate 9aab9af547 Fix wasm32 array lengths past 2^31 and pending top-level await exit codes
The backend converted size_t lengths and indexes to numbers with sitofp. On
wasm32 size_t is 32 bits, and an array length reaches 2^32 - 1, so a sparse
array of length 4294967295 read -1. sizeToDouble picks uitofp for a 32-bit
size_t and keeps sitofp for 64 bits, whose sign bit a length never reaches,
so native output is unchanged.

An unsettled top-level await always exited 13. Node exits with
`process.exitCode || 13` and runs the exit listeners first, which can set
process.exitCode again. scr_promise_finish_top_level now notes 13 only when
the program set no nonzero exit code, and the pending exit returns the
exit code after the listeners, as the other exits do.

Three WASI tests still exercised the embedded-JS fallback that native-only
compilation removed: an embedded-source fetch scan and two npm fixtures
that every target now refuses. They now check the same behavior with
static programs: fetch named only in text compiles, a global fetch call
reports SC3002, and CommonJS and ESM packages compile and match Node.
2026-10-15 13:39:03 -05:00
Chris Tate 23124ccdb0 Lower typed-array sort and toSorted 2026-10-14 11:15:37 -05:00
Malte Ubl b12b5b98c7 Keep NaN-coded integer reads and their compares as integers
A typed-array read at an unproven index joins the element with NaN for an
invalid index. For an integer element that join was `phi double [sitofp
x, NaN]`, which hides the integer from LLVM, and once `| 0` turned the NaN
arm into 0, InstCombine moved the `sitofp` of the result into the join
(`phi double [sitofp x, 0.0]`) where nothing folds it back: a loop over
`const c = units[i] | 0` converted every element to a double and back,
and compared it as a double.

An integer element now joins as `phi i32 [x, 0]` plus a validity bit and
converts after the join (`select valid, sitofp/uitofp x, NaN`). A declared
local whose every store is a bitwise result, and that a loop compares, also
keeps an i32 view: its stores already carry the integer, and compares of
the view stay integer compares, which also leaves the integer join with
more than one user, so the conversion is not folded into it.
2026-10-12 13:28:29 -07:00
Malte Ubl f9368ce8b4 Compute ToUint32 of large magnitudes without frem
The slow path of ToUint32 (NaN, the infinities and magnitudes above 2^53)
reduced the value with `frem`, which lowers to an `fmod` call. The call is
pure, so LLVM sometimes hoisted it above the range test onto the fast path
(65,865 calls in one tsc-ts parse).

A finite double above 2^53 is its integer mantissa shifted left by
exponent - 1075 >= 1, so its low 32 bits are the shifted mantissa's (none
once the shift reaches 64), negated for a negative value. NaN and the
infinities have the maximum exponent and give 0. The slow path is now
integer arithmetic on the double's bits with no branches.
2026-10-12 11:17:04 -07:00
Malte Ubl 81f8502387 Merge the fixed perf/alloc-hugepages into perf/exact-index-checks
Conflicts, all resolved to this branch's side:
- emitter.ts (numeric-table get, map index probe), expr-bytes.ts, local-array-reads.ts, string-reads.ts: the base only respelled the size-to-double conversions in the old range checks (uitofp nneg, now sitofp); exactIndexLines replaces those checks entirely. The base's other hunks in these files (host inline truncation, the remaining sitofp conversions) merge as they are.
- int32-slots.ts: #837 and #843 both extracted dynamicFieldStores with identical bodies; keep this branch's signature, whose optional records/unions maps also serve #837's single-argument callers.
2026-10-11 08:25:48 -07:00
Malte Ubl 2077bc9ee6 Spell non-negative size conversions as sitofp for clang 18
The `nneg` flag on `uitofp` is LLVM 19 syntax. Sanitized builds hand the textual module to the host clang, and Ubuntu 24.04's clang 18 rejects it ("expected type"), which failed every Linux sanitized Effect shard. These operands are size_t values below 2^63, so `sitofp` converts them identically, is accepted by every supported LLVM, and still lowers to one cvtsi2sd on x86-64 (LLVM 19+ canonicalizes it to `uitofp nneg` where it can prove the sign).
2026-10-11 08:13:58 -07:00
Malte Ubl ca009a0073 Keep the ToUint32 hint of NaN-coded typed-array reads
The hint is ToUint32 of the value, and ToUint32(NaN) is 0, so a read whose
index may be invalid keeps it (the invalid arm already contributed 0); it was
dropped unless the index was proven in bounds. Worker programs also dropped it
at the join with the shared-buffer path, which now converts its integer result
exactly. `hash ^ chars[i]` and similar operators then use the loaded integer
instead of re-deriving ToInt32 from the double.
2026-10-10 15:39:26 -07:00
Malte Ubl 4d3ed5ba81 Check unproven indexes with a signed round trip
A typed-array, inline ASCII charCodeAt, local array, constant-table or
number-map read whose index is not a proven integer decided validity with
two float compares against the converted length, then an fptoui/uitofp round
trip. On x86-64 both unsigned conversions expand to long sequences, and the
tsc-ts scanner pays them on every character it reads by position.

exactIndexLines replaces them with fptosi, freeze, sitofp, one float equality
and one unsigned integer compare against the length. A frozen out-of-range or
NaN conversion is an arbitrary integer that never converts back to the index
or is not below the length; below the length the round trip is exact, and -0
converts to 0 like ToPropertyKey(-0).
2026-10-10 15:39:25 -07:00
Malte Ubl 67f9de094c Convert lengths and in-range indices to doubles as non-negative
Array, string and byte lengths, counts, capacities and range-checked indices are size_t values below 2^63, but they were converted with a plain uitofp. On x86-64 without AVX-512 an unsigned 64-bit conversion expands to a five-instruction sequence with two constant-pool loads (unpcklps/subpd/unpckhpd/addsd), and it showed up in the hottest tsc-ts checker loops (compareTypeLists, getPropertyOfTypeEx) between a length load and the conversion back to an integer. `uitofp nneg` states what is already true, so the backend emits one cvtsi2sd. arm64 converts unsigned values in one instruction either way. Byte-number indices, which admit values up to 2^64, keep the unsigned conversion.
2026-10-10 14:47:55 -07:00
Malte Ubl 7d08a7441c Truncate inline on x86-64 and test typed-read presence without trunc
Baseline x86-64 has no rounding instruction, so every llvm.trunc became
a call to libm's trunc. The typed-array read helper paid that call on
every read: it decided presence with `i >= 0 && i < length &&
Math.trunc(i) === i` before the inline element load.

- Math.trunc (and the DataView offset truncation) now take an integer
  round trip on x86-64 targets: values of magnitude below 2^52 go through
  fptosi/sitofp with the sign restored by copysign (so -0.5 and -0 give
  -0); larger values, NaN and the infinities are already integral and
  keep their value. Other targets keep llvm.trunc, which is one
  instruction there.
- The ToUint32 slow path is only reached for magnitudes above 2^53 (and
  non-finite values), which are integers, so its truncation was a no-op
  and is gone.
- For integer element kinds, the read helper performs the element read
  that yields NaN for an invalid index (one inline index check) and
  answers undefined exactly when that read is NaN: an integer element is
  never NaN. Float element kinds keep the range and integrality test.
2026-10-10 14:03:20 -07:00
Malte Ubl b4a961b36d Read and write thread-owned typed arrays inline in worker programs
Worker programs sent every typed-array element read and write through
scr_bytes_get/scr_bytes_set, which take the shared-memory lock guard
even for buffers no other thread can see (4% of a Worker build of the
tsc-ts self-check). A view's `shared` storage is fixed before script
code can see it, so the emitted access now tests it: SharedArrayBuffer
views keep the locked runtime call, every other view takes the ordinary
inline path of non-worker programs.

The new corpus program reads and writes own and shared views through
the same functions (clamping, wrapping, float narrowing, out-of-range
and negative indices) on both threads.
2026-10-09 17:43:51 -07:00
Chris Tate cd28e4d6ca Answer undefined for out-of-range typed array reads like Node (#797) 2026-10-09 11:37:16 -05:00
Chris Tate 3e41bcbfd0 Make compile blockers and Node differences actionable (#786) 2026-10-09 10:30:31 -05:00
Chris Tate 095b619330 Add native workers and shared memory
- Add native worker execution with isolated module state, structured messages, and lifecycle controls.
- Support shared buffers and integer atomics with coordinated runtime ownership and cancellation.
- Extend compiler, runtime-pack, differential, and compatibility coverage for native concurrency.
2026-10-07 19:07:02 -05:00
Chris Tate c08ab955ae Enforce linting and consistent source formatting
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks.
- Preserve compiler import boundaries and protected fixtures while cleaning up lint findings.
- Standardize source formatting and validate import, fixture, and embedded-source protections.
2026-10-05 17:51:24 -05:00
Chris Tate 5de340f933 Optimize binary numeric pipelines
- Specialize standard Buffer and DataView numeric reads and writes while preserving checked errors, byte order, and unaligned access.
- Carry integer values through binary-processing loops and remove proven field bounds and receiver ownership overhead.
- Strengthen numeric IR validation and cover binary pipelines, edge cases, and native compiler parity.
2026-10-05 10:35:06 -05:00
Chris Tate d31bf3c4f8 Reduce ownership overhead across helpers and collections
- Infer stable heap inputs and local aliases while preserving ownership of escaping values.
- Remove redundant ownership work from collection and byte operations.
- Cover mutation, exceptions, indirect calls, and native bootstrap behavior.
2026-10-05 02:24:35 -05:00
Chris Tate 0328efe2e3 Optimize integer loops and typed-array access
- Carry exact integer facts through branches, arithmetic, and computed indexes.
- Lower ascending, descending, and strided loops with guarded integer induction.
- Remove proven typed-array bounds checks while preserving number semantics and target-width safety.
2026-10-05 00:54:17 -05:00
Chris Tate 6086bc1487 Reuse integer values across numeric operations
- Reuse integer values across local updates, branches, and numeric operations.
- Preserve typed-array integer values through reads and writes.
- Cover number semantics, evaluation order, and native emission boundaries.
2026-10-04 23:08:32 -05:00
Chris Tate 4d022037c3 feat: support static buffer constructors and prototype methods (#591)
* feat: support static buffer constructors and prototype methods

- Preserve typed-array constructor identity and shared storage across JavaScript class APIs.
- Support chained indexed assignments and mutable prototype methods with native receiver dispatch.
- Compile Three.js geometry merging, instancing, and keyframe interpolation without a JavaScript engine.

* fix: preserve native collection and dispatch semantics

- Preserve typed-array identity in collections and JavaScript argument evaluation.
- Keep symbol-backed fields and unreachable methods on their existing static paths.
- Restore compiler bootstrapping and deferred class diagnostics.

* fix: preserve inferred returns in native compilation

- Update recorded return statements in place so native compilation preserves mixed JavaScript return values.

* test: update inferred array coverage expectations

- Recognize heterogeneous writes supported by shared JavaScript array storage.
2026-09-30 17:53:59 -05:00
Chris Tate 24ab670305 Keep numeric typed arrays native across JavaScript boundaries
- Support integer widths and clamped arrays with shared storage and exact runtime brands.
- Preserve typed-array values through untyped fields, iteration, and byte I/O.
- Compile native ArrayBuffer response bodies and checked Number predicates.
2026-09-28 21:15:01 -05:00
Chris Tate f668fc4bfd Support native runtime values used by OpenTUI
- Add shared ArrayBuffer storage and native Unicode grapheme segmentation.
- Preserve builtin modules, globals, and byte views across JavaScript bindings.
- Compile package initialization expressions and optional native callables.
2026-09-28 19:01:50 -05:00
Chris Tate 76a086681a Support native byte queues and bundled JavaScript state
- Preserve nullable class fields and module callbacks in native storage with correct initialization behavior.
- Support typed-array construction, bulk copying, and in-place compaction through checked native values.
- Preserve receiver identity and evaluation order in nested field updates.
2026-09-28 09:54:27 -05:00
Chris Tate 7b007e03ef feat(compiler): bootstrap the LLVM emitter natively
- Run the production LLVM backend natively and reproduce its output across compiler generations.
- Replace unchecked emitter contexts with explicit shared state and callback hosts.
- Preserve NaN in serialized IR and respect optional record storage during spreads.
2026-09-27 22:29:24 -05:00
Chris Tate 3596f4ab13 Optimize proven integer arithmetic (#409) 2026-09-24 11:25:01 -05:00
Chris Tate 82b2417e53 perf: avoid redundant retains for numeric array reads (#404) 2026-09-24 00:36:29 -05:00
Chris Tate edc95464b8 feat: support static Float64Array (#391)
- Add Float64Array construction, storage, and access across compiler backends and runtime.

- Cover precision, views, methods, and dynamic boundaries with differential fixtures.
2026-09-23 19:13:18 -05:00
Chris TateandFábio Pereira c5129449ab perf(llvm): inline numeric bitwise operations (#366)
- Emit JavaScript bitwise operators with native i32 instructions.
- Cover native emission and hot-loop coercion semantics.

Fixes #357

Co-authored-by: Fábio Pereira <300015+redvulps@users.noreply.github.com>
2026-09-21 10:51:41 -05:00
Chris Tate 73ee01d7ef refactor(llvm): split expression emission by family (#235)
* refactor(llvm): split expression emission by family

- Extract expression and support emission into focused LLVM modules.
- Partition library calls behind prefix-dispatched domain modules.
- Consolidate keyed-record reads behind one shared implementation.

* fix(llvm): preserve validation call routing
2026-08-24 07:28:38 -05:00