-
Notifications
You must be signed in to change notification settings - Fork 648
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: Previous work in this stack on require calls has focused on making them faster, by caching the results for a particular module index. This cache is global to an execution. This diff attempts to identify redundant require calls statically within a function: essentially, making "require(n)" subject to CSE, by noting that require has special semantics that allow this. In particular, require(n) is idempotent (wrt subsequent calls for the same value of n): the first call may have side effects, but the second and subsequent calls do not. We make a special case for [metro-require] calls in CSEValue::canHandle to reflect this assumption. I add a statistic that breaks out the number of "require CSEs". I also considered doing a specialized optimization for require calls in Instruction::isIdenticalTo: instructions only get the [metro-require] attribute if we've proved that their first argument is the metroRequire function. So we'd assume the first argument to the all such calls are semantically the same in isIdentical (and in the hash function). This yielded a small number of additional require CSEs: on the order of 25 to 30. I give a range here because, try as I might, doing this made the results (in terms of # of CSE) non-deterministic. I was unable to figure out why -- I took care to make sure the hash function gave the same result whenever isIdentical would return true. I eventually decided that figuring out the mystery was not worth it, given the small extra benefit. Design question: in canHandle, we make a special case for metro-require. This seems perhaps less general than it might. In that function, we would all a call to be handled if it was labeled [pure]. The metro-require case is "as if pure"; for a given module Id, it has side effects, but only on the first call globally. So maybe we should have an [idempotent] attribute, in case we identify other functions with this property, and generalize canHandle in this way (pure || idempotent)? (The special assumption of equivalent first arguments is, however, specific to metro-require calls.) Reviewed By: neildhar Differential Revision: D64773199 fbshipit-source-id: d78538ac2cc12f10e6cfbaa4cc0879a826d09365
- Loading branch information
1 parent
c8d6f42
commit 9325986
Showing
3 changed files
with
100 additions
and
11 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
// RUN: %hermesc -O -dump-ir %s | %FileCheckOrRegen --match-full-lines %s | ||
|
||
// This test shows that require calls with the same module index are CSE'd | ||
// a dominating occurrence will be used for a subsequent occurrence. | ||
|
||
function fakeRequire(n) { | ||
switch (n) { | ||
case 7: | ||
return {blah: 100, bz: 200}; | ||
case 8: | ||
return {foo: 1000}; | ||
default: | ||
return null; | ||
} | ||
} | ||
|
||
print($SHBuiltin.moduleFactory( | ||
0, | ||
function(glob, require) { | ||
// The second require(7) should not have a call -- it should | ||
// re-use the result of the first. | ||
return require(7).blah + require(8).foo + require(7).baz; | ||
})(undefined, fakeRequire)); | ||
|
||
// Auto-generated content below. Please do not modify manually. | ||
|
||
// CHECK:scope %VS0 [] | ||
|
||
// CHECK:function global(): any | ||
// CHECK-NEXT:%BB0: | ||
// CHECK-NEXT: %0 = CreateScopeInst (:environment) %VS0: any, empty: any | ||
// CHECK-NEXT: DeclareGlobalVarInst "fakeRequire": string | ||
// CHECK-NEXT: %2 = CreateFunctionInst (:object) %0: environment, %fakeRequire(): functionCode | ||
// CHECK-NEXT: StorePropertyLooseInst %2: object, globalObject: object, "fakeRequire": string | ||
// CHECK-NEXT: %4 = TryLoadGlobalPropertyInst (:any) globalObject: object, "print": string | ||
// CHECK-NEXT: %5 = CreateFunctionInst (:object) %0: environment, %""(): functionCode | ||
// CHECK-NEXT: %6 = LoadPropertyInst (:any) globalObject: object, "fakeRequire": string | ||
// CHECK-NEXT: %7 = CallInst (:string|number|bigint) %5: object, %""(): functionCode, true: boolean, empty: any, undefined: undefined, undefined: undefined, undefined: undefined, %6: any | ||
// CHECK-NEXT: %8 = CallInst (:any) %4: any, empty: any, false: boolean, empty: any, undefined: undefined, undefined: undefined, %7: string|number|bigint | ||
// CHECK-NEXT: ReturnInst %8: any | ||
// CHECK-NEXT:function_end | ||
|
||
// CHECK:function fakeRequire(n: any): null|object | ||
// CHECK-NEXT:%BB0: | ||
// CHECK-NEXT: %0 = LoadParamInst (:any) %n: any | ||
// CHECK-NEXT: SwitchInst %0: any, %BB3, 7: number, %BB1, 8: number, %BB2 | ||
// CHECK-NEXT:%BB1: | ||
// CHECK-NEXT: %2 = AllocObjectLiteralInst (:object) empty: any, "blah": string, 100: number, "bz": string, 200: number | ||
// CHECK-NEXT: ReturnInst %2: object | ||
// CHECK-NEXT:%BB2: | ||
// CHECK-NEXT: %4 = AllocObjectLiteralInst (:object) empty: any, "foo": string, 1000: number | ||
// CHECK-NEXT: ReturnInst %4: object | ||
// CHECK-NEXT:%BB3: | ||
// CHECK-NEXT: ReturnInst null: null | ||
// CHECK-NEXT:function_end | ||
|
||
// CHECK:function ""(glob: any, require: any): string|number|bigint [allCallsitesKnownInStrictMode] | ||
// CHECK-NEXT:%BB0: | ||
// CHECK-NEXT: %0 = LoadParamInst (:any) %require: any | ||
// CHECK-NEXT: %1 = CallInst [metro-require] (:any) %0: any, empty: any, false: boolean, empty: any, undefined: undefined, undefined: undefined, 7: number | ||
// CHECK-NEXT: %2 = LoadPropertyInst (:any) %1: any, "blah": string | ||
// CHECK-NEXT: %3 = CallInst [metro-require] (:any) %0: any, empty: any, false: boolean, empty: any, undefined: undefined, undefined: undefined, 8: number | ||
// CHECK-NEXT: %4 = LoadPropertyInst (:any) %3: any, "foo": string | ||
// CHECK-NEXT: %5 = BinaryAddInst (:string|number|bigint) %2: any, %4: any | ||
// CHECK-NEXT: %6 = LoadPropertyInst (:any) %1: any, "baz": string | ||
// CHECK-NEXT: %7 = BinaryAddInst (:string|number|bigint) %5: string|number|bigint, %6: any | ||
// CHECK-NEXT: ReturnInst %7: string|number|bigint | ||
// CHECK-NEXT:function_end |
This file contains 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