[RFC] Elide Runtime Polyfill Checks By Adding Closure Typings w. -fstatic-builtins
Polyfills for ESNext functions are included in many modules. These polyfill functions slow down otherwise native functionality despite often being on critical paths (e.g. object spreads) and so reducing runtime work in these hot-paths is low-hanging fruit.
Typically, these functions would be hard to reason with as JS runtime allows almost all implementations to be overridden. Handily, Hermes already provides a -fstatic-builtins mode that allows us to assert that particular built-ins are frozen.
This PR attempts to take advantage of this to statically reason out any of the if (Object.keys) or typeof Object.keys === 'function' checks, which eliminates never-executed branches, and elides runtime conditional checks.
We do this in LowerBuiltinCalls, where we assert that these built-ins are of type closure and re-run the basic InstSimplify and SimplifyCFG. Empirically, these optimization re-runs at this BCGen level seem to have improved byte-code output notwithstanding.
Test Plan
Runtime execution was shown to have minimal-to-no difference.
| Version | Byte-Code Line Count |
|---|---|
| w. Changes | 2,424,160 |
| No Changes | 2,431,258 |
A common babel transform is interopRequireWildcard, which simplifies as follows:
Function<>(# params, # registers, # symbols):
Offset in debug table: source #, scope #, textified callees #
CreateEnvironment #
@@ -1155073,11 +1152371,7 @@ Offset in debug table: source #, scope #, textified callees #
#:
NewObject #
TryGetById #, #, #, "Object"
- GetById #, #, #, "defineProperty"
- JmpFalse #, #
- TryGetById #, #, #, "Object"
GetById #, #, #, "getOwnPropertyDes"...
-#:
Mov #, #
LoadConstUndefined #
GetPNameList #, #, #, #
An example of the improve byte-code produced where two entire branches are removed from object-shim:
-Function<shimObjectKeys>(1 params, 15 registers, 0 symbols):
-Offset in debug table: source 0x2c3d7b, scope 0x0000, textified callees 0x0000
- CreateEnvironment r1
- GetGlobalObject r0
- TryGetById r2, r0, 1, "Object"
- GetById r2, r2, 2, "keys"
- JmpTrue L1, r2
- TryGetById r3, r0, 1, "Object"
- GetEnvironment r2, 0
- LoadFromEnvironment r2, r2, 3
- PutById r3, r2, 1, "keys"
- Jmp L2
+Function<shimObjectKeys>(1 params, 14 registers, 0 symbols):
+Offset in debug table: source 0x2c27f6, scope 0x0000, textified callees 0x0000
+ CreateEnvironment r0
+ CreateClosure r4, r0, Function<>
+ LoadConstUndefined r3
+ LoadConstUInt8 r2, 1
+ LoadConstUInt8 r1, 2
+ Call3 r1, r4, r3, r2, r1
+ JmpTrue L1, r1
+ GetGlobalObject r1
+ TryGetById r1, r1, 1, "Object"
+ CreateClosure r0, r0, Function<keys>
+ PutById r1, r0, 1, "keys"
L1:
- CreateClosure r5, r1, Function<>
- LoadConstUndefined r4
- LoadConstUInt8 r3, 1
- LoadConstUInt8 r2, 2
- Call3 r2, r5, r4, r3, r2
- JmpTrue L2, r2
- TryGetById r2, r0, 1, "Object"
- CreateClosure r1, r1, Function<keys>
- PutById r2, r1, 1, "keys"
-L2:
+ GetGlobalObject r0
TryGetById r0, r0, 1, "Object"
GetById r0, r0, 2, "keys"
- JmpTrue L3, r0
- GetEnvironment r1, 0
- LoadFromEnvironment r0, r1, 3
-L3:
Ret r0
Finally, automated tests:
$ cmake --build ../build --target check-hermes all -j 4
[103/104] Running the Hermes regression tests
Testing Time: 22.90s
Expected Passes : 1767
Unsupported Tests : 62