Repository navigation
feat(runtime): arrays WinRT hands back, String[]/object arrays, boxed-argument leak - #44
Merged
Merged
Conversation
WinRT-allocated arrays now convert on both engines, where they were refused when the call was built: - T[] return values and received (out T[]) parameters. The callee's (length, data) pair is converted and the CoTaskMem array freed: primitive elements become a typed array of the matching kind (as interop.Array.create allocates), strings and objects a JS array, and structs an array of struct objects. - Boxed arrays (IReferenceArray<T>, e.g. from PropertyValue.Create*Array or a PropertySet value) unbox the same way boxed scalars already do. In the other direction, a typed array, or a plain array of strings, numbers, booleans or WinRT objects, passed for an Object parameter is boxed with PropertyValue, so a returned array can be passed back. Examples: CryptographicBuffer.CopyToByteArray(buffer, out) gives a Uint8Array; DisplayServices.FindAll() gives an array of DisplayId; ListBoxAutomationPeer.GetSelection() gives an array of wrappers. Arrays of strings or objects passed in (String[] parameters) are still unsupported.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
A JS string, number, boolean, struct shape or (since this branch) array passed for an Object parameter is boxed with PropertyValue, and the box's reference was forgotten so the pointer stayed valid through the call: every such argument leaked its box. Passing a 2 KB string 200k times grew the process by ~0.8 GB more than passing an existing WinRT object the same number of times. Each call now opens a BoxedArgs scope; boxes made while its arguments are parsed are released when the call returns. A call made during argument parsing opens its own scope and releases only its own boxes. Boxing outside any call keeps the reference as before.
- String[] and object-array parameters (Object[], IJsonValue[],
Uri[], ...) the caller passes or the callee fills, on both engines.
A passed JS array becomes HSTRINGs or objects queried to the element
interface, released after the call; a fill array (IVector<T>.GetMany)
takes a JS array as long as the slots wanted and gets the results
written back into it. interop.Array.create('String', n), ('Object',
n) or (TypeName, n) returns such an array.
- Array-typed property getters (e.g. CertificateExtension.Value)
convert like array returns.
GenericMethodCall, used only for open generic interfaces, refuses
string and object arrays rather than passing them as bytes.
triniwiz
force-pushed
the
feat/winrt-received-arrays
branch
from
November 6, 2026 08:24
47a3e7c to
6038bfb
Compare
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.
Follows #41. Arrays now convert in both directions for every element type, on both engines, including arrays WinRT allocates and hands back. Before, those were refused when the call was built ("Unsupported array return type"). Three commits:
1. Arrays WinRT hands back
A received array takes two ABI slots,
UINT32* lengthandT** data.MethodCallandPropertyCalllay these out for:ByRef T[]parameters, as two out slots.After the call,
(length, data)is converted and the CoTaskMem array freed. The element type comes fromclassify_return, through the newReturnKind::Array { element, element_type, size }:UInt8…Double,Boolean,Char16, enums)interop.Array.createreturnsStringObjectnullfor null)GuidString and object elements are taken over by the conversion, so nothing is left to release. Out parameters behave as they do for other types: classic accepts
interop.out()or returns[result, ...outs]; napi needsinterop.out().Boxed arrays:
IReferenceArray<T>values (fromPropertyValue.Create*Array, aPropertySet, settings, device properties) unbox the way boxed scalars already do, throughIPropertyValue.Get*Arrayand the same conversion. In the other direction, anObjectparameter boxes these withPropertyValue, so a returned array can be passed back:IReferenceArray<T>;Int32[]when they're all 32-bit integers,Double[]otherwise), all booleans, or all WinRT objects (or null).2. Boxed
Objectarguments no longer leakEvery JS value boxed for an
Objectparameter (strings, numbers, booleans, struct shapes, and now arrays) used toforgetits reference, so each argument leaked its box. Each call now opens aBoxedArgsscope, and the boxes made while parsing its arguments are released when the call returns. A call made during argument parsing (e.g. from a getter) opens its own scope and releases only its own boxes.Inserting the same values into a
PropertySet200k times (napi, memory after GC):Float64Array(256)The ~262 MB the no-boxing row still shows isn't a leak. It's napi finalizers waiting for the event loop, which this synchronous loop never yields to. With the loop yielding between 50k-call batches, memory levels off after a few batches.
3.
String[]and object arrays passed in or filledPropertyValue.CreateStringArray,IVector<T>.ReplaceAll, …): a JS array becomes HSTRINGs, or objects queried to the element's interface. They're released after the call.IVector<T>.GetMany): the caller passes a JS array as long as the slots it wants, and the results are written back into that array.interop.Array.create('String', n),('Object', n)or('Windows.Data.Json.IJsonValue', n)returns an array of nulls for this.[out]param attribute (ParameterDeclaration::is_fill_array).GenericMethodCall, which is only used for open generic interfaces, refuses these arrays rather than passing them as bytes.Testing
PropertyValuethat frees the returned HSTRINGsout byte[], including an empty oneString[]/Object[]passed in, and round trips throughPropertySetIVector<String>.ReplaceAll/GetMany, andGetManyreturning the filled countIVector<IJsonValue>.ReplaceAll/GetManyCertificateExtension.Valueset and getListBoxAutomationPeer.GetSelection()in a liveXamlWindownpm test: 442/442cargo test -p runtime --lib: the same failures asmainNot covered:
IReferenceArray<T>.Valuecalled directly on an interface instance. It goes through the same property path, but boxed arrays now unbox before JS can reach that member, so I couldn't test it.PropertyValue.CreatePointArray).