Skip to content

Add opt-in ClrArrayConversion.LiveView interop mode for CLR arrays#2721

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf/clr-array-live-view
Jul 21, 2026
Merged

Add opt-in ClrArrayConversion.LiveView interop mode for CLR arrays#2721
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf/clr-array-live-view

Conversation

@lahma

@lahma lahma commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Part of the V8-gap performance campaign (#1775 follow-up); completes the array-interop story started in #2716.

Change

New Options.Interop.ClrArrayConversion enum: Copy (default — exact current behavior, locked by tests) and LiveView. Under LiveView, a single-rank T[] crossing into script becomes a live, fixed-size ArrayWrapper<T> view — aligned with how wrapped List<T> already behaves — instead of a per-read deep-copied JsArray snapshot:

  • Index reads/writes go straight to the underlying CLR array (write-through both directions).
  • Any length-changing operation (push, growth, length write) throws a JS TypeError — never a leaking CLR NotSupportedException.
  • Array.isArray is false (matches the other wrappers; documented); for..of, spread and Array.prototype methods work via the attached array prototype; JSON.stringify still serializes as an array.
  • Multi-rank (T[,]) and non-zero-based arrays keep their existing behavior in both modes.
  • The flag-gated identity caches from Cover CLR arrays with the interop identity caches #2716 apply naturally to the wrappers, and all option checks stay out of the static _typeMappers memoization.

Numbers (default BDN job, InteropWrapperChurnBenchmark.ArrayMemberTraversal: 100 reads of an int[100] member per op)

mode Mean Allocated
Copy (default) 146.4 us 333,944 B
LiveView 59.4 us 83,544 B
Copy + TrackObjectWrapperIdentity 15.2 us 344 B
Copy + CacheRecentObjectWrappers 13.9 us 344 B

LiveView needs no identity flag for its 2.5× / −75% alloc win (live semantics, fresh wrapper per crossing); combined with an identity flag, array reads hit the wrapper cache like any other host object. Memory-probe verified: zero retained growth after full GC over 200k iterations.

Gates

Rebased on current main (includes #2716#2720): Jint.Tests 3689/3626 (net10/net472, includes 31 new LiveView tests), PublicInterface 114/114 both TFMs (API-approval snapshot updated for the new enum/property), Test262 99,431 passed / 0 failed. InteropTests.NonWritableArrayElement_* and ShouldConvertArrayToArrayInstance pass unchanged under default Copy.

A follow-up proposal issue for flipping the default to LiveView in the next major (aligning T[] with List<T> semantics) will be filed once this merges.

🤖 Generated with Claude Code

Options.Interop.ClrArrayConversion selects how CLR arrays cross into
script: Copy (default, per-conversion JsArray snapshot, behavior
unchanged) or LiveView, which exposes single-rank T[] as a fixed-size
ArrayWrapper<T> live view routed through the same WrapObjectHandler and
identity-cache machinery as other wrapped CLR objects.

- element reads/writes go straight to the underlying array in both
  directions; script writes are coerced to the element type
- resize attempts (push/pop/shift/unshift/splice, length writes,
  out-of-bounds index writes) throw a JS TypeError instead of leaking
  CLR exceptions; delete mirrors integer-indexed exotic semantics
  (false in range, true out of range) so pop/shift cannot zero slots
- Array.isArray stays false; iteration, spread, attached
  Array.prototype methods and JSON.stringify-as-array work like other
  array-like wrappers
- multi-rank (T[,]) and non-zero-based (T[*]) arrays keep Copy-mode
  behavior under both modes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lahma
lahma enabled auto-merge (squash) July 21, 2026 12:46
@lahma
lahma merged commit 749fc22 into sebastienros:main Jul 21, 2026
4 checks passed
lahma added a commit to lahma/jint that referenced this pull request Jul 21, 2026
Flip the default of Options.Interop.ArrayConversion from Copy to
LiveView so single-rank CLR arrays (T[]) cross into script as live,
fixed-size wrapper views instead of per-read deep-copied JsArray
snapshots -- aligning T[] with wrapped List<T> and cutting ~2.9x time /
~75% allocation on array-member traversal. Copy stays available to
preserve the pre-4.14 behavior.

Also rename the option (added in the unreleased sebastienros#2721) from
ClrArrayConversion to ArrayConversion, enum ArrayConversionMode: Interop
already implies CLR, and Copy/LiveView are exclusive modes (like
StepMode), not a combinable ...Type flags enum.

Migrate the tests that specifically exercised Copy-snapshot semantics
(array identity cache, sebastienros#2541 element writability) and four
extension-method tests to pin Copy explicitly: a CLR method returning
T[] now yields a wrapper that AsArray() rejects, and the LINQ .join()
case additionally resolves the Enumerable.Join extension method on the
wrapper's IEnumerable<T> target instead of falling back to native
Array.prototype.join (PreferJsPrototypeMethods mitigates). Refresh the
interop churn benchmark comments/config for the new default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lahma added a commit that referenced this pull request Jul 21, 2026
Flip the default of Options.Interop.ArrayConversion from Copy to
LiveView so single-rank CLR arrays (T[]) cross into script as live,
fixed-size wrapper views instead of per-read deep-copied JsArray
snapshots -- aligning T[] with wrapped List<T> and cutting ~2.9x time /
~75% allocation on array-member traversal. Copy stays available to
preserve the pre-4.14 behavior.

Also rename the option (added in the unreleased #2721) from
ClrArrayConversion to ArrayConversion, enum ArrayConversionMode: Interop
already implies CLR, and Copy/LiveView are exclusive modes (like
StepMode), not a combinable ...Type flags enum.

Migrate the tests that specifically exercised Copy-snapshot semantics
(array identity cache, #2541 element writability) and four
extension-method tests to pin Copy explicitly: a CLR method returning
T[] now yields a wrapper that AsArray() rejects, and the LINQ .join()
case additionally resolves the Enumerable.Join extension method on the
wrapper's IEnumerable<T> target instead of falling back to native
Array.prototype.join (PreferJsPrototypeMethods mitigates). Refresh the
interop churn benchmark comments/config for the new default.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant