Shape TypedArray constructors; modernize element codecs with BinaryPrimitives - #2582
Merged
Merged
Conversation
Shape the abstract TypedArrayConstructor and Uint8ArrayConstructor. The 11 concrete per-type constructors (Int8Array, Uint16Array, ... in TypedArrayConstructor.Types.cs) declare no [JsObject] of their own, so they inherit TypedArrayConstructor's shape and fill their per-type BYTES_PER_ELEMENT instance slot from the base ctor. Uint8ArrayConstructor already re-declares BYTES_PER_ELEMENT locally (for its base64 statics), so it re-implements IBuiltinShaped with its own self-contained shape — the same derived-from-shaped interface re-implementation used by the iterator prototypes. No inherited-[JsObject]-member support is needed. Benchmarks (BuiltinShapeBenchmark, default job, vs prior commit; EngineOnly control byte- identical at 13.70 KB): EngineInitConstructors (extended to touch several per-type constructors) 54.71 -> 53.66 KB (-1.05 KB). Conformance: Test262 TypedArray + ArrayBuffer + DataView 6120/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
RawBytesToNumeric (reads) and SetValueInBuffer (writes) hand-rolled the integer element
codecs: reads assembled values with manual bit-shifting per byte, and writes went through
NumericToRawBytes which allocated a scratch byte[] per element via BitConverter.GetBytes
(on every TFM except netstandard2.1) before an Array.Copy into the buffer.
Route every integer and bigint element type through System.Buffers.Binary.BinaryPrimitives
Read/Write{Int,UInt}{16,32,64}{Little,Big}Endian directly on the buffer span. This removes
the per-element byte[] allocation on the write path, drops the manual bit assembly on the
read path, and is endianness-correct regardless of the host's byte order (the old GetBytes +
conditional-reverse write path was only correct on a little-endian host). (short)/(ushort)
and (int)/(uint) share a bit pattern, so the signed overload serves the unsigned type too.
The floating-point path is deliberately unchanged: BitConverter round-trips Float16/32/64 with
the same NaN-canonicalization and big-endian scratch-buffer reversal as before (the write side
factored into FloatToRawBytes, Float16 reads into ReadFloat16 to keep the SUPPORTS_HALF
preprocessor branch out of a switch body). The per-instance _workBuffer now backs only the
big-endian float read reversal.
Benchmarks (new Jint.Benchmark/TypedArrayElementBenchmark.cs, default job; A/B/A to filter a
thermal outlier on the neutral float row). Allocation is the exact, mechanism-backed signal:
| Method | Time | Allocated |
|----------------|-----------------|------------------------|
| IntWriteRead | 1,225 -> 1,175 us (-4%) | 447.5 -> 287.5 KB (-35.8%) |
| DataViewMixed | 758 -> 731 us (-3.5%) | 265.1 -> 217.1 KB (-18.1%) |
| FloatWriteRead | 508 -> 491 us (noise) | 203.8 -> 203.8 KB (unchanged) |
Conformance: Test262 TypedArray + ArrayBuffer + DataView 6120/0 (2 skipped); a 35-case REPL
smoke test covering every element type, LE/BE round-trips, byte-order inspection, NaN
canonicalization, -0 preservation, Uint8Clamped rounding, and integer wrapping all pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
Uint8Array.prototype.setFromBase64 / setFromHex wrote the decoder's output into the target's backing buffer one byte at a time through SetValueInBuffer, so each byte paid a TypedArrayValue wrap plus the element-set switch and integer conversion — for a plain byte-for-byte Uint8 copy. FromBase64/FromHex already cap their output at the target's byte length, so the decoded bytes always fit within the typed array's view. Copy them straight into the backing buffer span with Span.CopyTo — a single memcpy instead of N element-set calls. Behavior is unchanged (the write count still comes from result.Bytes.Length; a target too small for a full base64 chunk still writes nothing, matching the prior loop). Conformance: Test262 TypedArray + ArrayBuffer + DataView + Uint8Array 6266/0 (2 skipped); REPL smoke tests for setFromHex/setFromBase64 incl. subarray offsets and partial/empty inputs match the previous loop byte-for-byte. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
lahma
enabled auto-merge (squash)
July 6, 2026 05:48
This was referenced Jul 13, 2026
This was referenced Jul 20, 2026
This was referenced Jul 28, 2026
This was referenced Aug 3, 2026
This was referenced Aug 14, 2026
This was referenced Aug 24, 2026
This was referenced Sep 1, 2026
This was referenced Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #2580 / #2581. Finishes the TypedArray corner of the built-in-shape rollout and, along the way, modernizes the TypedArray element codecs with current .NET idioms.
Shape the TypedArray constructor family. The abstract
TypedArrayConstructorandUint8ArrayConstructoropt into[JsObject(UseShape = true)]. The 11 concrete per-type constructors (Int8Array,Uint16Array, …) declare no[JsObject]of their own, so they inherit the base shape and fill their per-typeBYTES_PER_ELEMENTinstance slot from it;Uint8ArrayConstructorre-declaresBYTES_PER_ELEMENTlocally (for its base64 statics) and so re-implementsIBuiltinShapedwith its own self-contained shape — the same derived-from-shaped interface re-implementation used by the iterator prototypes.Modernize
JsArrayBufferelement read/write withBinaryPrimitives.RawBytesToNumeric/SetValueInBufferhand-rolled the integer codecs — reads assembled values with per-byte bit-shifting, and writes allocated a scratchbyte[]per element viaBitConverter.GetBytesbefore anArray.Copy. Every integer and bigint element type now goes throughBinaryPrimitives.Read/Write{Int,UInt}{16,32,64}{Little,Big}Endianstraight on the buffer span: no per-element allocation, no manual bit assembly, and endianness-correct regardless of host byte order (the old GetBytes+conditional-reverse write was only correct on a little-endian host). The floating-point path is deliberately unchanged (sameBitConverterround-trip, NaN canonicalization, and big-endian scratch reversal).Bulk-copy decoded base64/hex bytes into
Uint8Array.setFromBase64/setFromHexwrote the decoder output into the backing buffer one byte at a time throughSetValueInBuffer; since the target is Uint8 and the decoder caps output at the array's byte length, this is now a singleSpan.CopyTo.Benchmarks
Default BenchmarkDotNet job, each change A/B'd against its parent commit.
Built-in shape init (
BuiltinShapeBenchmark,EngineOnlycontrol byte-identical at 13.70 KB):EngineInitConstructors54.71 → 53.66 KB (−1.05 KB)Element read/write throughput (new
TypedArrayElementBenchmark, A/B/A to filter a thermal outlier on the neutral float row). Allocation is the exact, mechanism-backed signal:The integer/bigint write path no longer allocates a per-element scratch
byte[]; the float path is byte-identical in allocation and within noise on time, as expected.Conformance
Uint8ClampedArrayrounding and integer wrapping; plussetFromHex/setFromBase64with subarray offsets and partial/empty inputs — all matching prior behavior byte-for-byte.🤖 Generated with Claude Code
https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3