Repository navigation
Conversation
Summary: TypeInference repeats rounds over a group of functions until no type changes, and every round inferred every instruction in the group. Each round now infers the instructions of a function only when the function might infer differently: when its instructions changed in the previous round, its parameters changed in this round, or it uses a function whose return type or a variable whose type changed. Parameters, return types and variables are still inferred for the whole group every round. This gives the same types because, during the instruction sweep, a function's instructions only read types from the function itself, its parameters, the return types of known call targets and the variables it loads. Those cross between functions only in the parameter, return type, variable and reset steps, which mark the functions they affect. A Metro bundle forms a single group, since the bundle's top-level function creates every module factory. On a React Native app's production bundle (8.2 MB, 46k functions, 1.5M instructions), each TypeInference run took 12 to 15 rounds over all 1.5M instructions, though after the third round at most a few thousand changed type. `hermesc -O` on that bundle, best of 3: machine whole compile TypeInference Apple M5 7.85s -> 6.27s 3.73s -> 1.70s x86-64 Linux CI, GCC 13, iOS 26.18s -> 20.46s 12.14s -> 5.85s x86-64 Linux CI, GCC 13, Android 26.01s -> 20.25s 12.28s -> 6.77s The bytecode is byte-identical before and after for both the iOS and Android bundles, and check-hermes passes.
This branch has not been deployed
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
TypeInferencerepeats rounds over a group of functions until no type changes, and every round inferred every instruction in the group. On large inputs most of that work repeats results that cannot have changed. This PR makes each round infer a function's instructions only when the function might infer differently. Parameters, return types and variables are still inferred for the whole group every round, as before.A function's instructions are inferred again in the next round when:
This gives the same types because, during the instruction sweep, a function's instructions only read types from the function itself, its parameters, the return types of known call targets (
inferCallInst) and the variables it loads (inferLoadFrameInst). Types cross between functions only in the parameter, return type, variable and reset steps, and those steps now mark the functions they affect.do { infer params for every function // marks F for this round's sweep - infer instructions of every function + infer instructions of each marked function // marks F for the next round infer return types for every function // marks users of F for the next round infer types of every variable // marks users of V for the next round reset NoTypes once if nothing changed // marks what it resets } while (anything changed)Why it matters for React Native
A Metro bundle forms a single group: the bundle's top-level function creates every module factory, so
partitionFunctionsAndVarsreturns one partition. On a production React Native app bundle (8.2 MB of JavaScript, about 46k functions and 1.5M instructions), eachTypeInferencerun took 12 to 15 rounds over all 1.5M instructions, though after the third round at most a few thousand instructions changed type per round.TypeInferenceruns four times in an-Ocompile and was 42% to 47% of the compile.Instructions whose type changed in each round of the first
TypeInferencerun on that bundle:Test Plan
Same output. I compiled the same app's iOS and Android production bundles with
hermesc -emit-binary -O, using builds ofstatic_hat 72b0009 with and without this change. The bytecode is byte-identical for both bundles, on macOS (Apple M5, Apple clang) and on x86-64 Linux (GCC 13).Regression tests.
check-hermespasses with the change: 4,334 expected passes, 6 expected failures, 190 unsupported, no unexpected results.Compile time. Best of 3 runs of
hermesc -emit-binary -Oon the same bundles.TypeInferenceis the sum of its four entries in-ftime-report.TypeInferenceTo reproduce on any large Metro bundle: