Skip to content

Releases: sebastienros/jint

v4.16.2

Choose a tag to compare

@lahma lahma released this 09 Sep 15:35
730db51

Jint 4.16.2 is a maintenance release from the 4.x branch: correctness and conformance fixes backported from main, and nothing that changes an existing API or an existing default. If you are on 4.16.1 it is a drop-in update — every public signature is the one 4.16.0 shipped, on all five target frameworks, and the per-framework snapshots in Jint.Tests.PublicInterface/Verify/ are unchanged. main remains 5.0.0 development; what is coming there is recorded as it lands in docs/v5-migration.md.

Highlights

Failures that used to end the process, or never end. A native error raised while a call's arguments are being evaluated is propagated instead of leaving an empty value behind, which on 4.16.1 could recurse until the process died — decodeURIComponent on a malformed sequence was enough (#4009). Native recursion and the forwarding paths through bound functions and proxies are guarded so a deep native chain raises a catchable error (#4007). A module graph too deep to link raises an error the host can catch instead of overflowing the stack (#3548). Temporal and Intl parsing cannot throw an uncatchable RegexMatchTimeoutException because the machine was busy (#3543), a Temporal difference past a calendar's range raises RangeError instead of spinning forever (#3555), and the process-wide Intl culture cache and Temporal zone cache are read-only and bounded, with a rejected zone no longer remembered — closing a script-driven unbounded growth (#3546).

Generators and built-ins, step by step. A yield* delegation reached again by a loop both re-delegates and keeps its place: countdown(3) in a loop no longer hangs, and a delegating generator no longer returns the memoized first result (#3545). Array.prototype.map and slice hand a @@species constructor the length ToLength produced, and a non-callable map argument is a TypeError (#3547). A trailing NUL pads neither a numeric string nor an array index (#3552). A removed property slot is a tombstone rather than a free slot to reuse, so enumeration order survives a delete-and-readd (#3318), and LengthOfArrayLike no longer clamps through a uint overload (#3328).

Interop that answers for the right engine. Two engines in one process no longer decide each other's conversions and operators (#3559), a host type converter's answer stays with the engine whose converter gave it (#3563), and a value the host registers on a ShadowRealm — and the members its wrapper builds eagerly — belong to that realm (#3557). Realm construction state is restored after nesting or a failure (#4008). Overload selection is by the arguments in hand: an operator overload is chosen that way (#3611), a params overload is chosen by the array's element type with a failing element declining rather than throwing (#3782), an overload the argument cannot bind to is not a match, and a host operator that throws reports what it threw (#3554). An index on a wrapped host collection is one property however it is spelled, and a member filter that hides the indexer hides it (#3562); a read-only host collection refuses a write with a JavaScript TypeError rather than the CLR's NotSupportedException (#3556).

Internationalization and Temporal. The Persian calendar extends into proleptic years on its 33-year cycle, so the ends of Temporal's range land in the right Persian year (#4006); a calendar that counts Gregorian months writes their names (#3612); and a -u- extension carrying more than one key is read whole (#3613).

Errors. Only a string-valued stack counts as a pre-existing stack when a JavaScriptException is built, so an accessor or non-string stack on a thrown object no longer breaks error reporting (#3677, reported by @jeske).

Every change was verified failing-first against the unfixed branch on both .NET Framework and .NET 10, and the release was gated on a paired SunSpider and Dromaeo comparison against 4.16.1 on an idle machine: no row regressed outside run-to-run noise, most run 1–4 % faster.

What's Changed

  • Backport: a removed property slot is a tombstone, not a free slot to reuse (#3273) by @lahma in #3318
  • Backport: LengthOfArrayLike, delete the uint overload rather than clamp it (#3248) by @lahma in #3328
  • Temporal and Intl parsing cannot fail because the machine was busy (#3486) by @lahma in #3543
  • Backport: the process-wide Intl culture cache and Temporal zone cache are read-only and bounded, and a rejected zone is not remembered by @lahma in #3546
  • Array: map and slice hand a @@species constructor the length ToLength produced (#3510) by @lahma in #3547
  • Generators: a yield* delegation both re-delegates and keeps its place (backport of #3506 and #3518) by @lahma in #3545
  • A module graph too deep to link raises an error the host can catch, instead of ending the process (#3415) by @lahma in #3548
  • String to number: a trailing NUL pads neither a number string nor an array index (backport of #3544) by @lahma in #3552
  • Interop: a host operator reports what it threw, and an overload the argument cannot bind to is not a match by @lahma in #3554
  • Temporal: a difference past a calendar's range raises RangeError instead of spinning (#3452) by @lahma in #3555
  • Interop: a read-only host collection refuses script with a JavaScript error, not the CLR's own (backport of #3385) by @lahma in #3556
  • ShadowRealm: a value the host registers, and the members its wrapper builds eagerly, belong to that realm by @lahma in #3557
  • Interop: two engines in one process do not decide each other's conversions and operators (backport of #3521 and #3526) by @lahma in #3559
  • Interop: an index on a wrapped host collection is one property, and a filter that hides the indexer hides it by @lahma in #3562
  • Interop: a host type converter's answer stays with the engine whose converter gave it by @lahma in #3563
  • Interop: an operator overload is chosen by the arguments in hand (backport of #3578) by @lahma in #3611
  • Intl: a calendar counting Gregorian months writes their names (backport of #3589) by @lahma in #3612
  • Intl: a -u- extension carrying more than one key is read whole (backport of #3594) by @lahma in #3613
  • JavaScriptException: only a string "stack" counts as a pre-existing stack (#3607 backport) by @lahma in #3677
  • Interop: a params overload is chosen by the array's element type, and a failing element declines instead of throwing (#3764) by @lahma in #3782
  • Backport #3751 to 4.x: Temporal: the persian calendar extends into proleptic years on the 33-year cycle by @lahma in #4006
  • Backport #3922 to 4.x: Restore realm construction state after nesting or failure by @lahma in #4008
  • Backport #3845 to 4.x: Propagate native errors during call argument evaluation by @lahma in #4009
  • Backport #3877 to 4.x: Guard native recursion and forwarding paths by @lahma in #4007

Full Changelog: v4.16.1...v4.16.2

4.16.1

Choose a tag to compare

@lahma lahma released this 23 Aug 14:41
474996a

Jint 4.16.1 is the first release from the new 4.x maintenance branch, and it marks the point where the two lines separate: main is now 5.0.0 development, and 4.x is where the 4.16.x line continues.

What that means for you. If you are on 4.16.0, this is a drop-in update — it is correctness and conformance work only, no API change and no changed default. Every public signature is the same one 4.16.0 shipped, on all five target frameworks. If you want the 4.x line, take it from 4.x and expect fixes rather than features. If you want to follow where the engine is going, watch main — v5 brings breaking API changes, an opt-in WHATWG web API surface, Web Workers, Node compatibility and a raised .NET Framework floor, and every one of them is recorded as it lands in docs/v5-migration.md.

From this release onward the 4.x public surface is snapshotted per target framework in Jint.Tests.PublicInterface/Verify/, so "did the API move?" is a diff rather than a judgement call — on this branch a diff there is a bug, and comparing those files against main's is the v4→v5 delta.

Highlights

Conformance, from a suite that now runs more of test262. The staging/ directory is generated and executed for the first time (#3016), which is roughly 2,800 additional cases — largely SpiderMonkey's own suite contributed upstream, covering behaviour the stable directories never reach. Much of the work below is what it found.

Built-ins do what the spec says, step by step. The array built-ins perform the internal methods they name rather than equivalents (#3066); Array.from honours IsConstructor and a typed array's length write throws (#3043); an array truncation walks downwards and the generics report the writes they fail (#3072); argument validation and evaluation order are corrected in five built-ins (#3069); Map and Set get the [[SetData]] tombstone their traversals are specified over (#3073); Date.prototype.setTime stores the clipped time value (#3042); and Array.prototype.values/keys/entries no longer gate on an array-like receiver (#3236).

Iterators and control flow. A throw from the iterator step no longer closes the iterator (#3047); the done flag is consulted before stepping again (#3048); a rejected return() propagates out of an abandoned for await loop (#3113); an optional-chain short circuit is distinguished from a genuine undefined (#3040); a computed property key is evaluated even when spelled as a literal (#3039) and survives an await or yield intact (#3144, #3150); and destructuring the rest of an exhausted array yields an empty array rather than 2³² elements (#3263).

Numeric and string accuracy. Math.acosh, asinh, atanh, cbrt, expm1 and log1p are ported from fdlibm for correctly-rounded results across every target framework (#3050); toFixed formats from the double's exact value and reads this from [[NumberData]] (#3071); String.prototype case conversion derives from Jint's own Unicode tables rather than the host's culture data (#3068); and the regex engine is chosen per subject, with RegExp.prototype.replace no longer rewriting lastIndex (#3070).

Bounds that hold. JavaScript strings have a maximum length instead of a wrapped array rent (#3015); a JSON document too long to become a string is refused while it is being built (#3028); a frame displaced by a proper tail call keeps counting while its trampoline runs, so MaxRecursionDepth cannot be evaded by leaving and re-entering the trampoline (#3022); and an Atomics waiter is released when nothing can ever notify it again (#3029).

Error messages no longer run user JavaScript (#3041) — rendering a message for a value with a script-supplied toString used to invoke it, from inside the failure path.

Internationalization. The five Temporal members the proposal removed are dropped (#3014), and u-extension options are canonicalized with every date format the spec allows (#3018).

Two fixes in this release come from @svenrog — a sloppy function answering its own arguments (#3061) and the outer link on a parked Function-constructor environment (#3063).

What's Changed

  • Drop the five Temporal members the proposal removed by @lahma in #3014
  • Canonicalize u-extension options and format every date the spec allows by @lahma in #3018
  • Mark a global created by an unresolvable assignment, and stop a waitAsync timeout outliving its engine by @lahma in #3019
  • Run test262's staging/ directory too by @lahma in #3016
  • Give JavaScript strings a maximum length instead of a wrapped array rent by @lahma in #3015
  • Let a for-of frame decline the unwind it can only rethrow by @lahma in #3017
  • Keep counting a frame a tail call replaced while its trampoline runs by @lahma in #3022
  • Unpark staging/Temporal/removed-methods.js, which #3014 already fixed by @lahma in #3023
  • Drop the Islamic date conversions no calendar path reaches by @lahma in #3027
  • Let an Atomics waiter go when nothing can ever notify it again by @lahma in #3029
  • Refuse a JSON document too long to be a string while it is being built by @lahma in #3028
  • Bump the microsoft group with 3 updates by @dependabot[bot] in #3033
  • Bump the analyzers group with 1 update by @dependabot[bot] in #3031
  • Add initial threat model for untrusted scripts by @sebastienros in #3030
  • Stop ClassBenchmark rebuilding its engine per iteration by @lahma in #3053
  • createRealm installs a full $262 on the new realm and returns it by @lahma in #3044
  • Give the benchmark suite a measurement environment by @lahma in #3055
  • Evaluate a computed property key even when it is spelled as a literal by @lahma in #3039
  • Stop error messages from running user JavaScript by @lahma in #3041
  • Array.from honours IsConstructor, and a typed array's length write throws by @lahma in #3043
  • Consult the iterator's done flag before stepping it again by @lahma in #3048
  • Date.prototype.setTime must store the clipped time value by @lahma in #3042
  • Answer a sloppy function's own arguments instead of throwing by @svenrog in #3061
  • Keep the outer link on a parked Function-constructor environment by @svenrog in #3063
  • A throw from the iterator step must not close the iterator by @lahma in #3047
  • Attribute a cross-realm built-in's errors and result arrays to its own realm by @lahma in #3049
  • Distinguish an optional-chain short circuit from a genuine undefined by @lahma in #3040
  • Update every package the solution can update, and adapt to xUnit v4 by @lahma in #3064
  • Port fdlibm for acosh, asinh, atanh, cbrt, expm1 and log1p by @lahma in #3050
  • Separate the permanently declined test262 exclusions from the temporary ones by @lahma in #3065
  • Honor the receiver in an arguments object's [[Set]] and rebind a body var over a parameter by @lahma in #3067
  • Walk an array truncation downwards, and report the writes the generics fail by @lahma in #3072
  • Choose the regex engine per subject, and stop RegExp replace from rewriting lastIndex by @lahma in #3070
  • Format toFixed from the double's exact value and read this from [[NumberData]] by @lahma in #3071
  • Derive String.prototype case conversion from Jint's own Unicode tables by @lahma in #3068
  • Write Annex B's block-function var alias when the declaration is evaluated by @lahma in #3076
  • Settle a source-phase dynamic import with the module's [[ModuleSource]] by @lahma in #3074
  • Warm the stack-guard recovery test before it measures depth by @lahma in #3080
  • Maintenance: give the 4.x branch its CI, its version and its row in the README by @lahma in #3291
  • Backport fifteen correctness and conformance fixes for 4.16.1 by @lahma in #3298
  • Public API: record 4.x's shipped surface, so the v4 to v5 delta is a diff by @lahma in #3306

Full Changelog: v4.16.0...v4.16.1

v4.16.0

Choose a tag to compare

@lahma lahma released this 14 Aug 04:45
ff980b7

Jint 4.16.0 is a correctness- and reliability-focused release: alongside asynchronous module loading, proper tail calls and four new iterator built-ins, a pre-tag review swept the whole engine and fixed what it found — including long-standing defects that predate this cycle. No option defaults changed. Behaviour changes to note up front: JSON.stringify and other machine-readable output now format invariantly under every host culture — under Swedish or Finnish locales on .NET 8+ it used to emit a Unicode minus sign no JSON parser accepts; JSON.parse now rejects trailing commas as the grammar requires; bare identifiers at global scope resolve through the global's prototype chain per spec; IModuleLoader.Resolve is consulted once per (referrer, specifier) pair, so a loader using it as a per-import access-control checkpoint should move the check to LoadModule; and an inconsistent sort comparator now finishes with an implementation-defined order on every target framework instead of hanging (net462/netstandard) or throwing a CLR exception at script (net8+).

Highlights

Proper tail calls (#2975). Strict-mode calls in tail position reuse their frame, so "use strict" tail recursion runs in constant stack — the first ES2015 PTC implementation among the .NET engines.

Asynchronous module loading (#2872). IAsyncModuleLoader and the AsyncModuleLoader template let a host fetch module source over I/O without blocking a thread; Engine.Modules.StartImport returns an operation a game loop drives via ProcessTasks(), and ImportAsync awaits without holding a thread. The spec's load phase now exists as written, a warm-cache async loader keeps the blocking Import fully synchronous, and the blocking drain wakes on a work-arrived signal instead of polling. A module served over a transport keeps its whole url as Module.Location so its own relative imports resolve, a deferred namespace evaluates its module instead of exposing uninitialized bindings, and an import abandoned by a global snapshot restore reports itself faulted instead of polling forever.

The process no longer dies for recoverable reasons. Options.LimitRecursion used to kill the host process for most useful limits — the constraint fired, and the unwind itself overflowed the stack; exception filters now let it unwind ~7× deeper. The new opt-in Options.Constraints.StackOverflowGuard converts unbounded recursion — reachable through eighteen distinct routes, new, accessors, coercions and Proxy traps included — from a process kill into a catchable RangeError, exempting strict tail calls, which grow no stack. And a family of CLR exceptions that escaped engine.Evaluate past every script catch are now proper JavaScript errors or correct results: sorting with an inconsistent comparator, destructuring with a function-valued default (const { onChange = () => {} } = opts), toLocaleString outside DateTime's range, typed-array defineProperty without a value, DataView reads at 2³¹, String.replace $' with a lying exec, and the first instant of year 10000.

New built-ins. Iterator.prototype.join, chunks, windows and includes; take/drop now throw RangeError for a finite limit above 2^53−1 per the updated proposals. Intl.Locale.prototype.getCollations reports CLDR-cited collation data that Intl.Collator accepts in full, a malformed collation option is a RangeError, and Intl.supportedValuesOf("collation") derives from the same lists so the three can never drift.

Conformance, from a review that ran what the suite does not. Two of the fixed defects had test262 coverage only under the never-generated staging/ directory, and several had none at all: parseInt strips the sign before testing for a hex prefix, so parseInt("-0x10") is −16; a suspended finally no longer swallows a pending break/continue; a Proxy (or exotic host object) as the global's prototype answers bare identifiers through its get trap; Date.prototype.toISOString emits the spec's six-digit expanded year and round-trips through Date.parse in every spelling including year 0; iterator helpers close their receiver exactly once and only when the spec says so, and carry their own @@toStringTag; Map/Set size is the prototype accessor the spec defines rather than a phantom own property; a Proxy's defineProperty trap receives the partial descriptor the caller wrote; a string's @@iterator is read once, with the primitive as receiver; Array.prototype.join re-asks the array when a side effect fills a hole mid-join; a direct eval reaches the enclosing function's arguments in both modes; and Temporal.Now drops the methods the proposal removed.

Embedder surface. OperationDeadlineConstraint bounds a whole multi-entry host operation; ScriptPreparationOptions.StaticAnalysis trades prepare-time analysis for per-engine materialization on shared graphs; ModuleFactory.LocationOf exposes the module-naming rule a host must match; Engine.Advanced.HostDefined carries per-request state on a pooled engine; the CLR exception behind an interop error is reachable through JintException.TryGetClrException with opt-in ChainClrExceptions(), and a host method's own TargetException is no longer mistaken for a receiver mismatch; and a recursion-limit failure propagates out of a module load instead of becoming a catchable rejection.

Performance, gated. Against v4.15.3 on idle hardware, medians of three paired runs: controlflow-recursive −15.6% time and −40.4% allocation (proper tail calls), bitops-3bit-bits-in-byte −8.9%, math-spectral-norm −7.3%, crypto-sha1 −6.9%, 3d-raytrace −5.9%, math-cordic −5.8%, with a broad −1–4% tail across the call- and string-heavy rows; no row moved outside its own measured cross-run envelope in the other direction, and allocation is flat within ±0.2% suite-wide. Warmed parseInt call sites take the frameless fast-call lane (−13% on the parse loop), joined by the Number predicates, String.prototype.indexOf/startsWith/endsWith/includes/at/substr, global isNaN/isFinite and Array.isArray (−3% to −19%) and the Map/Set method family (map.get hit loop −13%); existence questions on a wrapped dictionary answer from ContainsKey, taking in −33% with −98% allocation and Object.keys −37%; resolving an inherited global no longer allocates per miss (−99.99% on the read loop) and a global created through an inherited write keeps the in-place store; JSON replacer/reviver eligibility is decided once per document, built-in callback dispatch once per loop, a call site's arguments reach an interpreted callee in registers, and function-local let/const live in fixed slots.

Breaking changes. Int32Extensions/Int64Extensions/DoubleExtensions — polyfill hosts that leaked into the public API — are now internal; on net462/netstandard2.0, code with using Jint; may have bound span Parse/TryParse members through them. JsonParser rejects trailing commas. Number.parseInt.length/Number.parseFloat.length report their spec values. Post-construction mutation of an Options instance no longer reaches an already-built engine, and Options.Configure callbacks work again. UnwrapIfPromise reports a cancelled engine as ExecutionCanceledException instead of a timeout. Time-zone matching is ASCII-case-insensitive per ECMA-402.

On the engine comparison benchmarks, Jint 4.16.0 is the fastest engine outright on 5 of 12 scripts — leading dromaeo-object-regexp-modern over native V8 by 1.25× — in a statistical tie for first on interop-collection-traversal, the fastest managed engine on 10 of 12, the fastest interpreter on all 12, and 8.6×–11.2× ahead of ClearScript (native V8) on every interop row while allocating 3.9×–12.4× less than the nearest managed competitor.

What's Changed

  • Run the repository's own host tests under Release-mode contract verification by @lahma in #2866
  • Update test262 suite and implement Iterator.prototype.join by @lahma in #2867
  • Stop a closing iterator from swallowing the error that closed it by @lahma in #2868
  • Give every benchmark row its own engine by @lahma in #2873
  • Hand a call site's arguments to an interpreted callee in registers by @lahma in #2874
  • Ask once per loop, not once per element, how to call a built-in's callback by @lahma in #2876
  • Say which spec document to read for a feature by @lahma in #2882
  • Run PR CI on every pull request, not only those targeting main by @lahma in #2883
  • Update test262 suite and adopt the new take/drop RangeError by @lahma in #2877
  • Implement Iterator Chunking by @lahma in #2878
  • Say to write against the modern BCL and polyfill downwards by @lahma in #2884
  • Implement Iterator Includes by @lahma in #2879
  • Ask once per document, not once per key, how to call a JSON replacer or reviver by @lahma in #2885
  • Use double.IsFinite instead of hand-rolled NaN and infinity pairs by @lahma in #2880
  • Bump the testing group with 1 update by @dependabot[bot] in #2889
  • Stop charging closures for per-engine lazy globals by @lahma in #2890
  • Bump the analyzers group with 1 update by @dependabot[bot] in #2888
  • Ma...
Read more

v4.15.3

Choose a tag to compare

@lahma lahma released this 29 Jul 11:09
a304aa5

Jint 4.15.3 rounds out the 4.15 embedder line: every item here answers friction a real integration reported while adopting the host-integration surface 4.15.0 introduced. Everything is additive — no option defaults changed and no behavior changes for existing code.

  • Engine.Advanced.AddLazyGlobal (#2862) — install a lazy global on a live engine, so a host whose globals are computed from per-request data can defer building them until script reads the name; the same PR adds Engine.Advanced.WithRestoredGlobals(snapshot, action), the try/finally every snapshot-reusing host was writing by hand.
  • PropertyDescriptor.CreateLazy (#2865) — a public lazy property descriptor that materializes once and then rejoins the read and write inline caches, which a hand-rolled CustomJsValue descriptor never could; it is the sanctioned way to build for any host object property what AddLazyGlobal does for a global.
  • Options.AddImmutableCrossing(params Type[]) (#2863) — a host promise that instances of the declared CLR types do not change while they are exposed to the engine, in exchange for which a wrapped object memoizes its resolved reads. On the nested-document walk it was built for that measures −43% to −84% time and −99% allocation against the undeclared path, with dictionary and JsonNode sources converging to identical steady-state cost. It is a promise: a declared object mutated anyway will serve stale reads.
  • Host-contract verification from the shipped package (#2864) — set the Jint.EnableHostContractVerification AppContext switch before the first use of any Jint type and the checks that catch a host answering one extension point in a way that contradicts another run in Release, throwing with a descriptive message. Embedders can now run their suites against the exact package they deploy instead of building a Debug Jint from source, and CI now runs this repository's own host suites that way too (#2866).
  • Engine.Advanced.HasSharedShape (#2861) — a stable, pinnable predicate for whether JsObject.Create, CreateFromEntries or JsObjectShape.Instantiate actually produced a shared-layout object, which the explicitly non-contractual ObjectRepresentation diagnostic could never be.
  • JsString.Create(string) is now public (#2860) — the counterpart of JsNumber.Create, answering the empty string and single-character ASCII from interned instances instead of allocating.
  • Documentation (#2859) — an unresolvable reference's Base holds an internal sentinel rather than undefined, and resolver authors returning it were leaking that sentinel string into scripts; the docs and the in-repo sample now show the right idiom.

What's Changed

  • Make JsString.Create(string) public by @lahma in #2860
  • Document that an unresolvable reference's base is a sentinel, not undefined by @lahma in #2859
  • Add engine-reuse ergonomics: post-construction lazy globals and a snapshot scope by @lahma in #2862
  • Give hosts a stable predicate for "did the shaping actually happen" by @lahma in #2861
  • Make the host-contract verifiers reachable, and complete by @lahma in #2864
  • Give hosts a lazy descriptor that rejoins the caches once it holds a value by @lahma in #2865
  • Let a host promise a wrapped object is immutable and have its reads memoized by @lahma in #2863

Full Changelog: v4.15.2...v4.15.3

v4.15.2

Choose a tag to compare

@lahma lahma released this 28 Jul 22:07
868df90

Jint 4.15.2 is a fix release.

  • Async and generator suspension — loop iteration state is preserved across suspensions in async generators and for await...of (#2852), an await suspending a right-hand side no longer stores the suspension sentinel into the target (#2855), and suspension-node resolution unwraps correctly (#2856).
  • Correctness — calling and instanceof work on bound functions whose target is itself bound (#2853), and inherited accessors reached through ObjectInstance.TryGetValue receive the original receiver (#2854).
  • Performance — the builtin-shape probe lane answers an authoritative miss without falling back to the slow path, which named-index misses on shaped objects were paying on every probe (#2858); and the JsObject.Create values span is now nullable-annotated so a lazy slot's required null needs no suppression (#2851).

What's Changed

  • Annotate the layout values span so a lazy entry's null needs no suppression by @lahma in #2851
  • Unwrap a JintStatement suspension node so for-await resumes into the right branch by @HermanusMuellerEU in #2856
  • Fix calling and instanceof on bound functions whose target is itself bound by @HermanusMuellerEU in #2853
  • Preserve loop iteration state across suspensions in async generators and for-await-of by @HermanusMuellerEU in #2852
  • Pass the original receiver to inherited accessors in ObjectInstance.TryGetValue by @HermanusMuellerEU in #2854
  • Do not store the suspension sentinel when an await suspends a right-hand side by @HermanusMuellerEU in #2855
  • Answer an authoritative miss from the builtin-shape probe lane by @lahma in #2858

New Contributors

Full Changelog: v4.15.1...v4.15.2

v4.15.1

Choose a tag to compare

@lahma lahma released this 28 Jul 11:30
64d734b

Jint 4.15.1 is a small refinement release shaped by the first real-world adoptions of 4.15.0's host-integration surface — every change answers a need a shipping embedder hit within days of the release. No behavior changes for existing code, with one deliberate spec-path improvement: Object.freeze no longer forces lazily-declared properties into existence just to validate attribute-only redefinitions (so freezing globalThis no longer materializes every lazy global).

  • JsObjectLayout lazy slots (#2850) — a fresh shaped object per item can now defer expensive members: declare AddLazy(name, factory) on the layout, pass per-instance state to JsObject.Create, and the member materializes on first read while every item keeps sharing one hidden class. In the motivating host shape (a 15-member event envelope with 4 expensive members), builds measure ~3.6× faster with 4× fewer allocations than the eager layout, and ~1.6× faster than the dictionary-mode workaround it replaces.
  • Observability for host testsEngine.Advanced.GetPropertyAccessSemantics (#2847) lets a test pin the access semantics the engine derived for a host type, and GetInteropConversionDiagnostics (#2848) counts CLR array crossings so a host can audit its ArrayConversion exposure — including through dependencies it doesn't own. Both carry the same non-contractual, diagnostics-only framing as GetObjectRepresentation.
  • PropertyFlag.NonWritable / OnlyConfigurable (#2849) complete the named combination lattice for the descriptor shapes hosts actually build.
  • Documentation (#2846) — the contracts a real adoption tripped over, stated where an embedder will find them: JsonSerializer reuse and its Undefined sentinel, the BigInt.prototype.toJSON escape hatch, what does not route through GetOwnProperties(), and the snapshot reuse recipe.

What's Changed

  • Document the contracts a real adoption tripped over by @lahma in #2846
  • Let a test observe the access semantics the engine derived for a host type by @lahma in #2847
  • Name the two PropertyFlag combinations hosts actually build by @lahma in #2849
  • Count CLR array conversions so a host can audit its crossing semantics by @lahma in #2848
  • Let a layout declare lazy slots so a shaped object can defer expensive members by @lahma in #2850

Full Changelog: v4.15.0...v4.15.1

v4.15.0

Choose a tag to compare

@lahma lahma released this 27 Jul 22:35
41ee60f

Jint 4.15.0 is an embedder-focused release: the host-integration surface was widened after auditing six real-world integrations, engine reuse got first-class support, and an adversarial pre-release review verified every change since 4.14.0 test-first. No option defaults changed. One behavior change to note: re-importing a module whose evaluation failed now rethrows the recorded error instead of returning a namespace (#2827).

Highlights

Host objects

  • Answer reads value-direct with TryGetOwnPropertyValue (#2808) and existence/enumerability questions without materializing descriptors with ProbeOwnProperty (#2803); access semantics are derived from the type automatically (#2804). Warm host reads cost zero probes, and Debug builds verify every answer.
  • ArrayLikeObject (#2835, #2841) projects a live indexed collection by implementing two members — indexed reads, for-of, spread, generics and JSON.stringify cost one virtual call per element.
  • JsObjectShape (#2830, #2836, #2840) declares shared prototypes once per process with lazily materialized per-realm members — and a shaped prototype can serve the prototype-method inline cache, which no host subclass can.
  • First adopter: a DOM binding cut indexed-read allocations by 60% and existence probes to zero.

Engine reuse

  • CaptureGlobalSnapshot / RestoreGlobalSnapshot (#2834) restore a configured global between evaluations: top-level let/const cleared (nothing else can), stale promise continuations fenced, warm per-engine caches kept. Configuration reuse — deliberately not an isolation boundary.
  • Fresh-engine hosts register globals lazily (AddLazyGlobal, #2805) or selectively via Prepared<T>.ReferencedGlobals (#2831). The two compose with the snapshot.

Interop

  • CLR member accessors are shared process-wide (#2798, made effective for extension-method hosts in #2829); compiled lanes cover dictionary writes, indexers, statics and omitted optional arguments (#2839); host delegates invoke through arity-typed thunks with no argument array (#2799, #2843).
  • Typed converter registration (#2794) and EnumConversionMode.Name (#2796) keep the lanes a blanket converter used to cost.
  • JSON parses from char and UTF-8 spans (#2832) and serializes into IBufferWriter<byte> (#2822).
  • NullPropagatingReferenceResolver.Instance (#2833) makes nullish member reads yield undefined through a recognized inline lane.

Performance, gated

  • Against 4.14.0 on idle hardware: every Dromaeo row improved (object-regexp −25% with 48% fewer allocations, object-string −21%, string-base64 −15%); SunSpider improved on eleven scripts, zero regressions.
  • Fast-call coverage widened across dozens of built-ins, with per-argument guards and register-based rest calls: Math.max(a,b) −22%, push(x,y) −19% (#2828, #2843, #2844).
  • encodeURI on clean input −85%; dense toReversed/with up to −86% (#2843).

On the engine comparison benchmarks, Jint 4.15.0 is the fastest engine outright on 5 of 12 scripts — taking dromaeo-object-regexp-modern from native V8 at −42% — the fastest managed engine on 10 of 12, the fastest interpreter on all 12, and 8.9×–11.6× ahead of ClearScript (native V8) on every interop row.

What's Changed

  • Replace xUnit Assert.* with AwesomeAssertions across the test suites by @lahma in #2742
  • Cache interop invokers process-wide instead of per-Engine by @lahma in #2743
  • Compile CLR property and field access instead of reflecting per hit by @lahma in #2744
  • Convert an indexer hit by the indexer type, not the member type by @lahma in #2746
  • Measure the execution timeout against an inline deadline by @lahma in #2747
  • Consult reference resolver for a call to an unresolvable identifier by @poissoncorp in #2750
  • Trim per-call overhead from the interop method fast lane by @lahma in #2745
  • Bypass the array-covariance check on exact-JsValue[] dense element stores by @lahma in #2751
  • Sort integer-index property keys by value instead of re-parsing each key by @lahma in #2752
  • Bypass the array-covariance check on JsValueListBuilder element stores by @lahma in #2753
  • Bypass the array-covariance check on JsObject overflow slot stores by @lahma in #2754
  • Bypass the array-covariance check on callback argument arrays (sort, groupBy, array iteration) by @lahma in #2755
  • Memoize the converted value of a stable reference-typed interop property by @lahma in #2756
  • Replace StrictModeScope with a Strict flag on the execution context by @lahma in #2757
  • CI: reliably seed the cross-OS Test262 cache and bump actions to latest by @lahma in #2758
  • Bump the testing group with 2 updates by @dependabot[bot] in #2760
  • Bump the analyzers group with 1 update by @dependabot[bot] in #2759
  • Bump the js-engine-comparisons group with 1 update by @dependabot[bot] in #2761
  • Refresh the engine comparison benchmark README against latest main by @lahma in #2762
  • Update Test262 suite to 5f1f06f0 by @lahma in #2763
  • Enable MA0212 and use MemoryMarshal.GetReference in the sum-of-products lane by @lahma in #2764
  • Enable MA0089 and use char separators/args for string methods by @lahma in #2766
  • Enable MA0209 and pass in-parameters explicitly at call sites by @lahma in #2768
  • Promote zero-violation Meziantou performance rules to warning by @lahma in #2765
  • Enable MA0168 (readonly struct for in/ref readonly parameter) by @lahma in #2767
  • Stop DromaeoBenchmark from measuring its own JIT warmup by @lahma in #2769
  • Hoist per-call constants out of the call and assignment fast paths by @lahma in #2770
  • Decide callability and receiver kind by type flag instead of type check by @lahma in #2771
  • Settle validated hops 1-3 slot hits inline by @lahma in #2772
  • Resolve the assignment slot store from one element ref and one context read by @lahma in #2773
  • Reinterpret non-Reference evaluation results without a checked cast by @lahma in #2774
  • Publish to nuget.org with trusted publishing instead of an API key by @lahma in #2748
  • Vectorize the special-casing pre-scans by @lahma in #2775
  • Search backwards for lastIndexOf with the vectorized span search by @lahma in #2776
  • Decide call-stack framing by type flag instead of a class-hierarchy walk by @lahma in #2777
  • Stop toWellFormed from rebuilding already well-formed strings by @lahma in #2778
  • Give the callback-free array methods a dense fast path, and make fill throw as the spec requires by @lahma in #2779
  • Hoist the separator decision out of Array.prototype.join's loop by @lahma in #2780
  • Cut dromaeo-object-regexp allocations by 47% via string-concat and RegExp fixes by @lahma in #2781
  • Add a fast-call lane for built-in function calls by @lahma in #2783
  • Stop the shared static member accessor cache from pinning engines by @lahma in #2784
  • Bump the testing group with 1 update by @dependabot[bot] in #2782
  • Box a coerced JS integer as the declared CLR type, not always as int by @lahma in #2785
  • Make TimeoutInterval replace the previous timeout instead of accumulating by @lahma in #2788
  • Remove the unreachable enum branch from the default object converter by @lahma in #2790
  • Refuse writes to read-only CLR members instead of shadowing them by @lahma in #2786
  • Document the constraint helpers' unlimited sentinels by @lahma in #2789
  • Keep the compiled invoker lane for return types object converters cannot observe by @lahma in #2791
  • Thread the member resolution requirement through the API it came from by @lahma in #2787
  • Add an option to expose CLR enums to script as their name by @lahma in #2796
  • Give every engine its own constraint instances so Options can be shared by @lahma in #2795
  • Stop the custom reference resolver from disabling the property-write fast path by @lahma in #2797
  • Add benchmark lanes for th...
Read more

v4.14.0

Choose a tag to compare

@lahma lahma released this 22 Jul 11:54
a19544d

Jint 4.14.0 is an interop-focused performance release: CLR arrays now cross into script as live views instead of copies, recently wrapped host objects reuse their wrappers, single-candidate interop method calls dispatch through compiled invokers, and JSON.parse interns repeated keys and values. Host collection traversal is 10.9× faster than 4.13.0. Two interop defaults changed in this release — read the first two highlights if you pass CLR arrays to scripts or rely on per-crossing conversion behavior; everything else needs no code changes to benefit.

Highlights

CLR arrays are live views by default (behavior change). Options.Interop.ArrayConversion now defaults to ArrayConversionMode.LiveView (#2721, #2728, #2735): a single-rank T[] crossing into script becomes a live, fixed-size view over the underlying array — the way wrapped List<T> already behaves — instead of being copied into a new JS array on every read. Writes go through in both directions, and arrays exposed through read-only-declared members (e.g. IReadOnlyList<T>) produce read-only views. Iteration, Array.prototype methods, JSON serialization, index-key enumeration (Object.keys / for..in yield "0".."n-1") and undefined for out-of-range reads all behave array-like, but Array.isArray returns false, and because CLR arrays are fixed-size, resizing operations (push/pop/length writes) throw a TypeError like integer-indexed exotic objects do — shift/splice may move elements before their length change throws, as for typed arrays. Set Options.Interop.ArrayConversion = ArrayConversionMode.Copy to restore the 4.13 behavior.

Recently wrapped CLR objects reuse their wrappers (behavior change). The new Options.Interop.CacheRecentObjectWrappers defaults to true (#2734): a small bounded ring (8 entries, keyed by reference identity and exposed type) reuses wrappers for host objects that repeatedly cross into script. Wrapper identity becomes stable (host.Obj === host.Obj), script-attached state (freeze, defineProperty, expandos) survives crossings, and the per-crossing wrapper allocation disappears. Under Copy array conversion this also means repeated reads of the same CLR array reuse the first JsArray snapshot while it stays cached — CLR-side mutations are not re-copied; set the option to false for the pre-4.14 fresh-snapshot-per-crossing behavior. Engine.Dispose() releases the ring.

Interop fast lanes. Single-candidate method calls run through a compiled invoker that binds and invokes without argument arrays or boxing (#2733), with per-parameter binding flags precomputed (#2719). Resolved ObjectWrapper members get a per-call-site inline cache (#2722) and the member-call fast path covers primitive string receivers (#2717). Array-like wrapper creation is a cached factory call with lazily materialized length (#2730), primitive elements convert without boxing on both indexed reads and Array.prototype iteration (#2731, #2735), the wrapper identity caches cover CLR arrays (#2716), and implicitly implemented interface methods are deduplicated in member resolution (#2711).

JSON. JSON.parse interns property keys and string values within a parse, parses numbers off the span with an exactly-rounded fast path and scans string content in bulk (#2718, #2725, #2732) — the json-parse-modern comparison row is 6% faster with 23% less allocation than 4.13.0. Parsing is also aligned with the JSON grammar (#2738): malformed numbers like -09 and 1. are now rejected as in V8, while raw U+2028/U+2029 in strings and escaped control characters in keys — both valid JSON — are now accepted.

Strings. Chained slice/substring and split segments stay zero-copy views (#2720), whole-string substring/substr return the receiver, and mismatched-length comparisons no longer materialize views (#2740).

Execution constraints at host boundaries. Timeouts and cancellation are re-checked when control returns from host CLR code, so detection latency is bounded by one host call instead of a statement-count window, without adding per-statement cost — gated on execution depth so host-side reads of wrapped objects on an idle engine never observe a stale timer (#2713, #2714, #2715). Execution-context depth stays balanced when constraint exceptions unwind generator/async frames, and a host callback that re-enters the engine no longer resets the outer script's budget (#2736).

Correctness (including a pre-release review). A review of everything since 4.13.0 fixed: spurious TDZ when a for-header reads a name the loop body shadows (#2709) and stale closure captures from destructuring defaults in for-loop headers (#2739); the compiled-invoker lane now defers to custom ITypeConverters and preserves reflection exception types (#2737); and the new wrapper defaults were hardened — declared-type contracts for arrays (an IReadOnlyList<T>-typed member no longer yields a writable view), a static type-mapper poisoning crash, Engine.Dispose releasing the wrapper caches, and JS-array in/enumeration/out-of-range semantics on array views (#2735). Closure reads memoize slot-cache chain reachability (#2726).

On the engine comparison benchmarks, Jint 4.14.0 beats ClearScript (native V8) by 7.1×–9.1× on every script ↔ host interop row — host collection traversal went from last to second among all engines at 15,597 → 1,433 µs with 99% less allocation — while remaining the fastest managed engine on 10 of 12 pure-JS scripts and the fastest interpreter on all 12, and now leading array-stress and dromaeo-object-array, rows V8 narrowly led at 4.13.0.

What's Changed

  • Refresh EngineComparison benchmarks for 4.13.0 by @lahma in #2704
  • Fix spurious TDZ when a for-header reads a name the loop body shadows by @svenrog in #2709
  • Bump the testing group with 1 update by @dependabot[bot] in #2710
  • Add tests for using modules from script code run via Evaluate by @lahma in #2712
  • Deduplicate implicitly implemented interface methods in member resolution by @lahma in #2711
  • Re-check amortized constraints at interpreter/host-code boundaries by @lahma in #2713
  • Add ClearScript V8 to engine comparison benchmarks, trim suite, add script-to-host interop suite by @viceice in #1775
  • Gate host-boundary constraint checks on active evaluation and harden coverage by @lahma in #2714
  • Key the host-boundary constraint gate on execution depth and close remaining lanes by @lahma in #2715
  • Cover CLR arrays with the interop identity caches by @lahma in #2716
  • Extend the member-call fast path to primitive string receivers by @lahma in #2717
  • Intern object property keys within a single JSON parse by @lahma in #2718
  • Precompute per-parameter interop binding flags by @lahma in #2719
  • Keep slice-of-slice and split segments zero-copy by @lahma in #2720
  • Add opt-in ClrArrayConversion.LiveView interop mode for CLR arrays by @lahma in #2721
  • Cache resolved ObjectWrapper members per member-expression node by @lahma in #2722
  • Refresh engine comparison README after the V8-gap campaign by @lahma in #2723
  • Bulk string scanning and a simple-number fast path for JSON.parse by @lahma in #2725
  • Memoize slot-cache chain reachability for closure reads by @lahma in #2726
  • Refresh engine comparison tables after the second campaign round by @lahma in #2727
  • Default Interop.ArrayConversion to LiveView for 4.14 by @lahma in #2728
  • Cache array-like wrapper factories and materialize length lazily by @lahma in #2730
  • Convert primitive array-like wrapper elements without boxing by @lahma in #2731
  • Add a compiled-invoker fast lane for single-candidate interop method calls by @lahma in #2733
  • Intern JSON.parse string values and parse numbers off the span by @lahma in #2732
  • Default Interop.CacheRecentObjectWrappers to true for 4.14 by @lahma in #2734
  • Harden LiveView array wrappers and interop wrapper caches for 4.14 by @lahma in #2735
  • Keep execution-context depth balanced under raw constraint exceptions by @lahma in #2736
  • Decline the compiled-invoker fast lane for custom type converters and foreign receivers by @lahma in #2737
  • Align JSON.parse with the JSON grammar for numbers, line separators and keys by @lahma in #2738
  • Decline for-loop environment reuse when a pattern default embeds a closure by @lahma in #2739
  • Skip materialization on mismatched-length string compares and whole-string substrings by @lahma in #2740
  • Refresh engine comparison benchmarks for 4.14.0 by @lahma in #2741

Full Changelog: https://g...

Read more

v4.13.0

Choose a tag to compare

@lahma lahma released this 15 Jul 12:39
c244f92

Jint 4.13.0 is a performance- and correctness-focused release. It brings a Proxy overhaul — trap dispatch rebuilt to forward with near-zero allocation, plus a new public API for implementing traps in .NET — extends the unboxed interpreter fast lanes to more operators and loop shapes, and cuts allocations on for..of, nested-function calls and array enumeration. A thorough pre-release review of everything since 4.12.0 also fixed several correctness bugs. No code changes are required to benefit.

Highlights

Proxy overhaul, and a CLR trap API. Proxy trap dispatch was rebuilt around a shared skeleton with lazy argument construction and pooled arrays, so a proxy with no matching trap forwards to its target with effectively zero allocation (#2674, #2675, #2676). Proxies can now be implemented from .NET: Engine.Advanced.CreateProxy / CreateRevocableProxy accept a ProxyHandler whose virtual methods are the traps, with the same invariant enforcement as JavaScript handlers (#2678). Several Proxy spec fixes came along — getPrototypeOf / setPrototypeOf with null prototypes (#2668), the construct trap's argument array (#2670), capturing [[Construct]] at creation (#2669), and the get trap firing for a property named revoke (#2667) — and the ObjectWrapper iterator helpers are hardened against foreign and revoked receivers (#2681).

Interpreter fast lanes. New unboxed operand lanes for the arithmetic binary operators (#2664) and an int32 fast lane for remainder (#2671) remove per-iteration boxing; flag-proven casts use Unsafe.As on the hot paths (#2673) and JsNumber.Create avoids a native fmod (#2662). Strict-equality guards against undefined / null / typeof are fused (#2658), member-expression identifier reads route through the identifier caches (#2660), and the identifier slot cache is restructured hop-0-first (#2689). The tight-loop fast lane now covers while and do-while bodies (#2688).

Lower allocations. for..of over an array no longer allocates an iterator-result object per element (#2700); per-call nested-function instantiation is allocation-free (#2684); for-in over arrays enumerates dense indices lazily without materializing a key list (#2656); and observation-only constraint checks are amortized so tight loops stay fast under a timeout (#2672).

RegExp. Quantified groups without capture or lookaround hazards prefer the .NET Regex engine (#2682), reused .NET adaptations adaptively upgrade to RegexOptions.Compiled (#2690), and the custom engine's match timeout is enforced by an inline deadline rather than a thread-pool timer (#2686).

Correctness (including a pre-release review). A review of everything since 4.12.0 fixed: a regex routing regression that silently truncated matches for nullable non-capturing quantified groups (#2694) and a custom-engine bug dropping iterations for multi-atom quantified groups (#2699); Proxy trap dispatch is now atomic against a mid-dispatch revoke (#2696); top-level await of a .NET Task in a module (#2665), plus prompt cancellation of the await drain (#2697); the arguments object escaping a short-circuiting logical compound assignment un-materialized (#2698); for-in now includes inherited enumerable index properties on Array.prototype (#2655); and the memory limit stays exact in tight loops (#2695).

Across the managed JavaScript engines for .NET, Jint 4.13.0 is the fastest engine on 17 of the 21 comparison scripts — and the fastest interpreter on all 21 — while allocating far less memory than the other engines; dromaeo-3d-cube is ~9% faster and dromaeo-string-base64 ~10% faster than 4.12.0. See the engine comparison benchmarks for the full table.

What's Changed

  • Drop Jurassic from engine comparison benchmarks, add Okojo by @lahma in #2653
  • Add benchmark lanes for for-in over arrays, holey-array traversal and large-chunk string concat by @lahma in #2654
  • Fix for-in missing inherited enumerable index properties placed on Array.prototype by @lahma in #2655
  • for-in over arrays: enumerate dense indices lazily without materializing a key list by @lahma in #2656
  • Harden the known wall-clock test flakes by @lahma in #2657
  • Fuse strict-equality guards: x === undefined/null and typeof x === "literal" by @lahma in #2658
  • Skip the prototype walk for array hole reads when the chain is provably clean by @lahma in #2659
  • Route member-expression identifier-object reads through identifier caches by @lahma in #2660
  • Avoid native fmod in JsNumber.Create(double) integral detection by @lahma in #2662
  • Add unboxed operand lane for arithmetic binary operators by @lahma in #2664
  • Fix top-level await of a .NET Task in a module failing with "Pending" by @lahma in #2665
  • Add Proxy trap dispatch benchmarks by @lahma in #2666
  • Fix Proxy get trap being bypassed for properties named "revoke" by @lahma in #2667
  • Add int32 fast lane to the raw-double remainder sites by @lahma in #2671
  • Fix Proxy getPrototypeOf/setPrototypeOf traps with null prototypes by @lahma in #2668
  • Capture Proxy [[Construct]] at creation per ProxyCreate by @lahma in #2669
  • Replace flag-proven casts with Unsafe.As on hot arithmetic paths by @lahma in #2673
  • Fix Proxy construct trap receiving Array-constructor-mangled arguments by @lahma in #2670
  • Amortize observation-only constraint checks and keep tight loops armed under timeouts by @lahma in #2672
  • Refactor Proxy trap dispatch into a shared skeleton with lazy argument construction by @lahma in #2674
  • Pool Proxy trap argument arrays by @lahma in #2675
  • Slim allocations in Proxy ownKeys trap validation by @lahma in #2676
  • Give every Proxy invariant violation a descriptive TypeError message by @lahma in #2677
  • Add ProxyHandler for implementing Proxy traps in .NET code by @lahma in #2678
  • Guard default RevocableProxy instances with a clear exception by @lahma in #2679
  • Add CLR ProxyHandler lanes to ProxyBenchmark by @lahma in #2680
  • Harden ObjectWrapper iterator helpers against foreign and revoked receivers by @lahma in #2681
  • Let branchy constructors with static this-stores shape from instance three by @lahma in #2685
  • Prefer .NET Regex for quantified groups without capture or lookaround hazards by @lahma in #2682
  • Skip covariant store checks when populating exact JsValue arrays by @lahma in #2683
  • Enforce custom regex-engine timeout via an inline deadline instead of a thread-pool timer by @lahma in #2686
  • Make per-call nested function instantiation allocation-free by @lahma in #2684
  • CI: cross-OS Test262 suite cache + action/tool version bumps by @lahma in #2687
  • Extend the tight-loop lane to while and do-while statements by @lahma in #2688
  • Restructure identifier slot-cache hot paths as hop-0-first by @lahma in #2689
  • Adaptively upgrade reused .NET regex adaptations to RegexOptions.Compiled by @lahma in #2690
  • Update dependencies to latest version by @lahma in #2692
  • Update Test262 suite to f2d143564 and drop now-fixed exclusion by @lahma in #2693
  • Fix nullable non-capturing quantified regex groups mis-routed to .NET Regex by @lahma in #2694
  • Keep the memory limit exact instead of amortizing it in tight loops by @lahma in #2695
  • Make Proxy trap dispatch atomic against mid-dispatch revoke; guard ToObject() by @lahma in #2696
  • Observe cancellation during the top-level-await event-loop drain by @lahma in #2697
  • Materialize arguments escaping a short-circuiting logical compound assignment by @lahma in #2698
  • Fix custom regex engine dropping iterations for a multi-atom quantified group by @lahma in #2699
  • Skip per-element IteratorResult allocation in value-kind array for-of by @lahma in #2700
  • Add value-producing slot fast path for identifier ++/-- expressions by @lahma in #2703
  • Fuse x == null / x == undefined loose-equality guards by @lahma in #2702
  • Compose &&/|| and ! through unboxed GetBooleanValue in boolean contexts by @lahma in #2701

...

Read more

v4.12.0

Choose a tag to compare

@lahma lahma released this 12 Jul 15:18
c37378b

Jint 4.12.0 is a performance- and correctness-focused release. It completes the move to hidden-class shapes across the whole object model, extends the unboxed interpreter fast lanes to more operators and call shapes, and adds a layer of per-engine caching so re-executed scripts and re-created functions reuse their compiled metadata and environments. A pre-release review of everything since 4.11.0 also fixed several correctness regressions. No code changes are required to benefit.

Highlights

Object model — shapes everywhere. The hidden-class shape model now backs the built-in prototypes and constructors, TypedArrays, the global object, and Intl / Temporal (#2580, #2581, #2582, #2590, #2595, #2597). JSON.parse builds its result objects as shapes, so an array of like-shaped records costs one allocation per record instead of a property dictionary each (#2634). Object literals inside generator/async frames and object spread {...src} adopt shapes too (#2596, #2648, #2635), and a provably-simple constructor shapes its instances from the third construction (#2636).

Interpreter fast lanes. New unboxed operand lanes for equality, bitwise, modulo-equality and sum-of-products expressions remove per-iteration boxing (#2602, #2604, #2611, #2628), and comparison operands are served from the validated global-descriptor cache (#2603). Expression-only and if/else for-loop bodies run through a tight per-iteration cycle with a member-bound loop test (i < arr.length) (#2605, #2617, #2623), env-less leaf calls run against the captured environment directly (#2627), and functions that cannot observe their this skip this-binding (#2626).

Caching & reuse. Nested-scope global reads and writes are served from a validated global-binding cache (#2584, #2625); hoisted function and class definitions, and the top-level statement handler tree, are reused across re-evaluations on an engine (#2613, #2615, #2649); and for-of / for-in reuse a fixed-slot per-iteration environment, skipping per-iteration TDZ re-init where it is provably safe (#2586, #2632).

Lower allocations. A coverage campaign added benchmarks for common patterns the suite did not exercise and then closed the hotspots they surfaced (#2630): resolved await chains and engine-internal promise reactions (#2639), for-in enumeration (#2640), throw/catch (#2641), primitive number/boolean/bigint methods (no wrapper object, #2642), and tagged templates (#2638) all allocate far less.

Correctness. Fixes for sticky + global [Symbol.match] returning wrong results (#2600), an unlabeled break escaping a labeled switch (#2607), -0 in integer multiplication (#2620), and raw property writes on shaped hosts (#2591, #2601). A pre-release review (#2651) additionally fixed for-in re-enumerating a shadowed key (a mid-loop delete and a pooled-iterator reuse case), mapped-arguments writes being lost after the call returns (and duplicate-parameter mapping now follows the spec), and hardened the object-literal and built-in-shape paths.

Across the managed JavaScript engines for .NET, Jint 4.12.0 is the fastest engine on 17 of the 21 comparison scripts — and the fastest interpreter on all 21 — leading by up to ~5.4× over the next-fastest engine while allocating 2×–63× less memory than the closest competitor. See the engine comparison benchmarks for the full table.

What's Changed

  • Built-in shapes for prototypes and constructors: composable storage, accessors, aliases by @lahma in #2580
  • Shape Number/%TypedArray% constructors and %TypedArray%.prototype; add [JsInstanceSlot] by @lahma in #2581
  • Shape TypedArray constructors; modernize element codecs with BinaryPrimitives by @lahma in #2582
  • Trim per-iteration block ceremony; slot lane for lexical declaration initialization by @lahma in #2583
  • Serve nested-scope global reads and writes from the global-binding cache by @lahma in #2584
  • Probe own-property existence/enumerability without materializing descriptors by @lahma in #2585
  • Keep builtin-shape deopt lazy: unmaterialized function slots stay deferred by @lahma in #2588
  • Serve arguments reads virtually; defer property materialization by @lahma in #2589
  • Reuse a fixed-slot per-iteration environment in for-of/for-in by @lahma in #2586
  • Bump the analyzers group with 3 updates by @dependabot[bot] in #2575
  • BuiltinShape Factory slots: shape-path intrinsic references and throwers; flip Function.prototype by @lahma in #2590
  • Fix raw SetProperty on builtin-shaped hosts landing in an invisible dictionary by @lahma in #2591
  • Build the yield argument handler once, not per evaluation by @lahma in #2593
  • Allocation-free for-in key enumeration by @lahma in #2592
  • Extend prepared-script engine-retention test to newer env cache shapes by @lahma in #2594
  • Shape-backed object literals inside generator/async frames when values cannot suspend by @lahma in #2596
  • Flip Intl, Temporal and %AsyncFromSyncIteratorPrototype% to builtin shapes by @lahma in #2597
  • GlobalObject joins the builtin-shape system via hybrid shape+overflow storage by @lahma in #2595
  • Refresh engine comparison benchmark results by @lahma in #2598
  • Update Test262 suite to latest commit; drop issue-5066 slice exclusion by @lahma in #2599
  • Fix sticky+global [Symbol.match] .NET fast path returning wrong results by @lahma in #2600
  • Make raw SetProperty before lazy initialization survive Initialize by @lahma in #2601
  • Add unboxed slot-number lane to equality operators by @lahma in #2602
  • Run expression-only for-loop bodies through a tight per-iteration cycle by @lahma in #2605
  • Serve comparison-lane operands from the validated global-descriptor cache by @lahma in #2603
  • Add dense-array append fast path for computed-index writes by @lahma in #2608
  • Mark dead completion values at script/eval top level by @lahma in #2606
  • Fused modulo-equality lane; admit const bindings to comparison-lane reads by @lahma in #2604
  • Construct new Date() from clock ticks without intermediate conversions by @lahma in #2609
  • Fix unlabeled break escaping a labeled switch statement by @svenrog in #2607
  • Flatten eligible loop-body lexicals into the pooled loop environment by @lahma in #2610
  • Evaluate sum-of-products assignments on raw doubles, boxing once by @lahma in #2611
  • Refresh engine comparison benchmark results by @lahma in #2612
  • Reuse hoisted function definitions across re-evaluations on an engine by @lahma in #2613
  • Reuse class member definitions across class re-evaluations by @lahma in #2615
  • Add decorator hook for CLR interop resolution errors by @lahma in #2614
  • Fix ForBencmark class name typo by @lahma in #2618
  • Re-resolve slot locations when a shared handler tree changes function instance by @lahma in #2616
  • Member-bound loop test lane for i < arr.length / i < s.length by @lahma in #2617
  • Add [JsSymbolAlias] for symbol-keyed function-identity aliases by @lahma in #2619
  • Preserve -0 in integer multiplication fast paths by @lahma in #2620
  • Refresh engine comparison benchmark results by @lahma in #2621
  • Reduce per-call ceremony: thread FDI state, collapse suspendable probes by @lahma in #2622
  • Extend for-loop tight-body lane to if/else chains and variable declarations by @lahma in #2623
  • Add call-site constructor cache with zero-arg leaf fast path for new Date() by @lahma in #2624
  • Memoize nested-scope global reads behind chain identity and an injection epoch by @lahma in #2625
  • Skip OrdinaryCallBindThis for functions that cannot observe their this-binding by @lahma in #2626
  • Run env-less leaf calls against the captured environment directly by @lahma in #2627
  • Add unboxed operand lane for bitwise operators over identifiers by @lahma in #2628
  • Refresh engine comparison benchmark results by @lahma in https://github.com/sebastienros/j...
Read more