Skip to content

Commit

Permalink
Emit a fast path for Function.prototype.call
Browse files Browse the repository at this point in the history
Summary: Time needed to execute attached microbenchmark: 280ms -> 170ms.

Reviewed By: fbmal7

Differential Revision: D65348301

fbshipit-source-id: c21efcad04b7d15e93339dd1b093dbdd96dad1ba
  • Loading branch information
avp authored and facebook-github-bot committed Nov 22, 2024
1 parent 94a2419 commit 6d4da33
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* 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.
*
* @format
*/

(function() {

function foo() { 'noinline'; }

var N = 10_000_000;
var start = Date.now();
for (var i = 0; i < N; ++i) foo.call();
var end = Date.now();
print("Time: " + (end - start));

})();
53 changes: 51 additions & 2 deletions lib/Optimizer/Scalar/LowerBuiltinCalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class LowerBuiltinCallsContext {

/// Identifier of "apply".
const Identifier applyID;
/// Identifier of "call".
const Identifier callID;

private:
/// Map from a builtin object to an integer "object index".
Expand All @@ -73,7 +75,8 @@ class LowerBuiltinCallsContext {

LowerBuiltinCallsContext::LowerBuiltinCallsContext(StringTable &strTab)
: hermesInternalID(strTab.getIdentifier("HermesInternal")),
applyID(strTab.getIdentifier("apply")) {
applyID(strTab.getIdentifier("apply")),
callID(strTab.getIdentifier("call")) {
// First insert all objects.
int objIndex = 0;
#define NORMAL_OBJECT(object)
Expand All @@ -91,6 +94,7 @@ LowerBuiltinCallsContext::LowerBuiltinCallsContext(StringTable &strTab)
#include "hermes/FrontEndDefs/Builtins.def"

calleeNamesToTryOptimize_.insert(applyID);
calleeNamesToTryOptimize_.insert(callID);
}

hermes::OptValue<BuiltinMethod::Enum>
Expand Down Expand Up @@ -192,11 +196,56 @@ static bool tryOptimizeKnownCallable(
BaseLoadPropertyInst *loadProp,
LiteralString *propLit) {
assert(callInst && loadProp && propLit && "no nullptrs");
// TODO: Optimize call().

auto &builtins = LowerBuiltinCallsContext::get(F->getParent());
IRBuilder::InstructionDestroyer destroyer;

if (propLit->getValue() == builtins.callID) {
// Add a fast path for "call" by splitting the basic block and trying to
// simply call the function if it's known to be Function.prototype.call.
BasicBlock *oldBB = callInst->getParent();
BasicBlock *newBB = splitBasicBlock(oldBB, callInst->getIterator());

BasicBlock *fastBB = builder.createBasicBlock(F);
BasicBlock *slowBB = builder.createBasicBlock(F);

builder.setInsertionBlock(oldBB);
builder.createBranchIfBuiltinInst(
BuiltinMethod::HermesBuiltin_functionPrototypeCall,
callInst->getCallee(),
fastBB,
slowBB);

builder.setInsertionBlock(fastBB);
llvh::SmallVector<Value *, 8> args{};
// f.call(this, arg1, arg2, ...)
// ^ start here
// CallInst.getArgument(0) is the "this" value.
for (unsigned i = 2, e = callInst->getNumArguments(); i < e; ++i) {
args.push_back(callInst->getArgument(i));
}
auto *fastCall = builder.createCallInst(
callInst->getThis(),
/* newTarget */ builder.getLiteralUndefined(),
/* thisValue */ callInst->getNumArguments() > 1
? callInst->getArgument(1)
: builder.getLiteralUndefined(),
args);
builder.createBranchInst(newBB);

builder.setInsertionBlock(slowBB);
auto *branchToNew = builder.createBranchInst(newBB);
callInst->moveBefore(branchToNew);

builder.setInsertionPoint(&*newBB->begin());
auto *phi = builder.createPhiInst();
callInst->replaceAllUsesWith(phi);
phi->addEntry(fastCall, fastBB);
phi->addEntry(callInst, slowBB);

return true;
}

if (propLit->getValue() == builtins.applyID &&
callInst->getNumArguments() == 3 &&
llvh::isa<CreateArgumentsInst>(callInst->getArgument(2))) {
Expand Down
52 changes: 52 additions & 0 deletions test/Optimizer/call-fastpath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* 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 -target=HBC -dump-ir %s | %FileCheckOrRegen --match-full-lines %s

function foo(f, a, b) {
f.call(a, b);
f.call(b, a);
}

// Auto-generated content below. Please do not modify manually.

// CHECK:scope %VS0 []

// CHECK:function global(): undefined
// CHECK-NEXT:%BB0:
// CHECK-NEXT: %0 = CreateScopeInst (:environment) %VS0: any, empty: any
// CHECK-NEXT: DeclareGlobalVarInst "foo": string
// CHECK-NEXT: %2 = CreateFunctionInst (:object) %0: environment, %foo(): functionCode
// CHECK-NEXT: StorePropertyLooseInst %2: object, globalObject: object, "foo": string
// CHECK-NEXT: ReturnInst undefined: undefined
// CHECK-NEXT:function_end

// CHECK:function foo(f: any, a: any, b: any): undefined
// CHECK-NEXT:%BB0:
// CHECK-NEXT: %0 = LoadParamInst (:any) %f: any
// CHECK-NEXT: %1 = LoadParamInst (:any) %a: any
// CHECK-NEXT: %2 = LoadParamInst (:any) %b: any
// CHECK-NEXT: %3 = LoadPropertyInst (:any) %0: any, "call": string
// CHECK-NEXT: BranchIfBuiltinInst [HermesBuiltin.functionPrototypeCall]: number, %3: any, %BB2, %BB3
// CHECK-NEXT:%BB1:
// CHECK-NEXT: %5 = LoadPropertyInst (:any) %0: any, "call": string
// CHECK-NEXT: BranchIfBuiltinInst [HermesBuiltin.functionPrototypeCall]: number, %5: any, %BB5, %BB6
// CHECK-NEXT:%BB2:
// CHECK-NEXT: %7 = CallInst (:any) %0: any, empty: any, false: boolean, empty: any, undefined: undefined, %1: any, %2: any
// CHECK-NEXT: BranchInst %BB1
// CHECK-NEXT:%BB3:
// CHECK-NEXT: %9 = CallInst (:any) %3: any, empty: any, false: boolean, empty: any, undefined: undefined, %0: any, %1: any, %2: any
// CHECK-NEXT: BranchInst %BB1
// CHECK-NEXT:%BB4:
// CHECK-NEXT: ReturnInst undefined: undefined
// CHECK-NEXT:%BB5:
// CHECK-NEXT: %12 = CallInst (:any) %0: any, empty: any, false: boolean, empty: any, undefined: undefined, %2: any, %1: any
// CHECK-NEXT: BranchInst %BB4
// CHECK-NEXT:%BB6:
// CHECK-NEXT: %14 = CallInst (:any) %5: any, empty: any, false: boolean, empty: any, undefined: undefined, %0: any, %2: any, %1: any
// CHECK-NEXT: BranchInst %BB4
// CHECK-NEXT:function_end

0 comments on commit 6d4da33

Please sign in to comment.