Repository navigation
Conversation
50c2c78 to
45d4e5f
Compare
Object::forEachProperty to JSI
|
This breaks the ABI. We need to discuss whether that is worthwhile, compared to adding a new interface. The doc-comment on the new function is not great. It says "he callback must take in four arguments...", which is redundant, but does not explain any restrictions on the callback - can it allocate, can it modify the object that is being enumerated, can it throw, etc. There is no explanation of what happens with property names that are not convertable to UTF-8 either, Additionally, converting to UTF-8 really is not JSI-s job, especially given that not everything can be converted. For all of these reasons, I think this needs a careful redesign. |
45d4e5f to
7da714f
Compare
Object::forEachProperty to JSIfa6266d to
e550757
Compare
…lues IPropertyEnumeration::forEachProperty calls a callback with the name and the value of each enumerable property of an object. The result is the same as calling getProperty for each name of getPropertyNames, without the names array and a String for each name. It's a separate, optional interface, so IRuntime and its ABI don't change. A runtime without it returns nullptr from castInterface, and the caller uses getPropertyNames. It isn't behind JSI_UNSTABLE: if the design has to change after a release, a runtime can stop returning this interface and provide a new one, and callers fall back to getPropertyNames in the meantime. The callback gets the name like getStringData gives the data: an ascii flag, then char or char16_t units, so names aren't converted to UTF-8 and every name comes through unchanged. The callback may use the runtime, so Hermes copies each name into a reused buffer.
e550757 to
ad25bb8
Compare
|
Thanks for the review, I agree with all three points. I've updated the PR: ABI. I didn't put it behind One thing I noticed: UTF-8. The conversion is gone. The callback gets the name the way Doc comment. I rewrote it. It now defines the behavior as the same as calling
The new |
Summary
React Native reads every props object with
Object::getPropertyNames, thengetValueAtIndex,getString,getProperty(String)andutf8for each name: about four JSI calls, three pinned values and two name conversions per property.This adds a JSI interface that does the whole walk in one call:
The result is the same as calling
getPropertyfor each name ofgetPropertyNames. The doc comment defines the details: the names are collected before the first call, getters and Proxy traps run, what happens to properties that are added or deleted during the enumeration, and how exceptions from getters and from the callback stop it.castInterface, soIRuntimeand its ABI don't change. A runtime without it returnsnullptr, and the caller usesgetPropertyNames.TracingRuntimereturnsnullptrfor it too, so a trace records the calls of that fallback.JSI_UNSTABLE, so React Native can use it as soon as a Hermes release has it. Because it's optional, freezing it is cheap: if the design has to change after a release, a runtime can stop returning this interface and provide a new one, and callers fall back togetPropertyNamesin the meantime. I can move it behindJSI_UNSTABLEif you prefer.getStringData: anasciiflag, thencharorchar16_tunits. JSI doesn't convert names to UTF-8, and every name comes through unchanged, including unpaired surrogates.getStringData, Hermes copies each name into a buffer that it reuses, and the name is valid only during the call.getNamedOrIndexed, the same way asgetProperty(PropNameID). There's no names array and nojsi::Stringper name.RuntimeDecoratorandWithRuntimeDecoratorforwardcastInterfaceto the plain runtime, so calls through the interface of the thread-safe runtime don't take its lock. That's the same forISerializationtoday.Test Plan
unittests/API/APITest.cpp, formakeHermesRuntimeandmakeThreadSafeHermesRuntime:ForEachPropertyMatchesGetProperty: for objects with string, index, non-ASCII, unpaired surrogate, symbol, inherited, getter and non-enumerable properties, an array, a Proxy and an empty object, the names and values are the same as withgetPropertyNames+getProperty. The callback allocates and runs a garbage collection, and the name stays valid.ForEachPropertyWhenTheObjectChanges: a property that the callback deletes is still visited withundefined, and a property that it adds isn't visited.ForEachPropertyStopsOnExceptions: a getter that throws stops the enumeration with aJSError, an exception from the callback stops it and propagates, and the runtime stays usable.APITests: all 446 tests pass withoutJSI_UNSTABLEand all 517 with it.synthTraceandtraceInterpreterbuild in both configurations.APITests(macOS host, Release,makeHermesRuntime, median of 9 samples of 200,000 iterations, not part of this PR) for the walk React Native does: names, values and name lengths, on objects with number-valued props.getPropertyNames+ stringsforEachProperty