Commit 51274d3c authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Introduce the notion of context-sensitivity for JS operators.

This change adds predicates to check whether a given JavaScript operator
needs the "current context" or if any surrounding context (including the
"native context") does it. For example JSAdd doesn't ever need the
current context, but actually only the native context. In the
BytecodeGraphBuilder we use this predicate to check whether a given
operator needs the current context, and if not, we just pass in the
native context.

Doing so we improve the performance on the benchmarks given in the
tracking bug significantly, and go from something around

  arrayMap: 476 ms.
  arrayFilter: 312 ms.
  arrayEvery: 241 ms.
  arraySome: 152 ms.

to

  arrayMap: 377 ms.
  arrayFilter: 296 ms.
  arrayEvery: 191 ms.
  arraySome: 91 ms.

which is an up to 40% improvement. So for idiomatic modern JavaScript
which uses higher order functions quite a lot, not just the builtins
provided by the JSVM, this is going to improve peak performance
noticably.

This also makes it possible to completely eliminate all the allocations
in the aliased sloppy arguments example

```js
function foo(a) { return arguments.length; }
```

concretely we don't allocate the function context anymore and we also
don't allocate the arguments object anymore (the JSStackCheck was the
reason why we did this in the past, because it was holding on to the
current context, which also kept the allocation for the arguments
alive).

Bug: v8:6200, v8:8060
Change-Id: I1db56d00d6b510ce6337608c0fff16af96e95eef
Design-Document: bit.ly/v8-turbofan-context-sensitive-js-operators
Reviewed-on: https://chromium-review.googlesource.com/c/1267176Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56441}
parent eeb15e84
......@@ -3468,7 +3468,9 @@ Node* BytecodeGraphBuilder::MakeNode(const Operator* op, int value_input_count,
memcpy(buffer, value_inputs, kPointerSize * value_input_count);
Node** current_input = buffer + value_input_count;
if (has_context) {
*current_input++ = environment()->Context();
*current_input++ = OperatorProperties::NeedsExactContext(op)
? environment()->Context()
: jsgraph()->HeapConstant(native_context());
}
if (has_frame_state) {
// The frame state will be inserted later. Here we misuse the {Dead} node
......
......@@ -179,6 +179,12 @@
V(JSCreateWithContext) \
V(JSCreateBlockContext)
#define JS_CALL_OP_LIST(V) \
V(JSCall) \
V(JSCallForwardVarargs) \
V(JSCallWithArrayLike) \
V(JSCallWithSpread)
#define JS_CONSTRUCT_OP_LIST(V) \
V(JSConstructForwardVarargs) \
V(JSConstruct) \
......@@ -186,11 +192,8 @@
V(JSConstructWithSpread)
#define JS_OTHER_OP_LIST(V) \
JS_CALL_OP_LIST(V) \
JS_CONSTRUCT_OP_LIST(V) \
V(JSCallForwardVarargs) \
V(JSCall) \
V(JSCallWithArrayLike) \
V(JSCallWithSpread) \
V(JSCallRuntime) \
V(JSForInEnumerate) \
V(JSForInNext) \
......
......@@ -7,6 +7,7 @@
#include "src/compiler/js-operator.h"
#include "src/compiler/linkage.h"
#include "src/compiler/opcodes.h"
#include "src/runtime/runtime.h"
namespace v8 {
namespace internal {
......@@ -18,6 +19,109 @@ bool OperatorProperties::HasContextInput(const Operator* op) {
return IrOpcode::IsJsOpcode(opcode);
}
// static
bool OperatorProperties::NeedsExactContext(const Operator* op) {
DCHECK(HasContextInput(op));
IrOpcode::Value const opcode = static_cast<IrOpcode::Value>(op->opcode());
switch (opcode) {
#define CASE(Name) case IrOpcode::k##Name:
// Binary/unary operators, calls and constructor calls only
// need the context to generate exceptions or lookup fields
// on the native context, so passing any context is fine.
JS_SIMPLE_BINOP_LIST(CASE)
JS_CALL_OP_LIST(CASE)
JS_CONSTRUCT_OP_LIST(CASE)
JS_SIMPLE_UNOP_LIST(CASE)
#undef CASE
case IrOpcode::kJSCloneObject:
case IrOpcode::kJSCreate:
case IrOpcode::kJSCreateLiteralArray:
case IrOpcode::kJSCreateEmptyLiteralArray:
case IrOpcode::kJSCreateLiteralObject:
case IrOpcode::kJSCreateEmptyLiteralObject:
case IrOpcode::kJSCreateArrayFromIterable:
case IrOpcode::kJSCreateLiteralRegExp:
case IrOpcode::kJSForInEnumerate:
case IrOpcode::kJSForInNext:
case IrOpcode::kJSForInPrepare:
case IrOpcode::kJSGeneratorRestoreContext:
case IrOpcode::kJSGeneratorRestoreContinuation:
case IrOpcode::kJSGeneratorRestoreInputOrDebugPos:
case IrOpcode::kJSGeneratorRestoreRegister:
case IrOpcode::kJSGetSuperConstructor:
case IrOpcode::kJSLoadGlobal:
case IrOpcode::kJSLoadMessage:
case IrOpcode::kJSStackCheck:
case IrOpcode::kJSStoreGlobal:
case IrOpcode::kJSStoreMessage:
return false;
case IrOpcode::kJSCallRuntime:
return Runtime::NeedsExactContext(CallRuntimeParametersOf(op).id());
case IrOpcode::kJSCreateArguments:
// For mapped arguments we need to access slots of context-allocated
// variables if there's aliasing with formal parameters.
return CreateArgumentsTypeOf(op) == CreateArgumentsType::kMappedArguments;
case IrOpcode::kJSCreateBlockContext:
case IrOpcode::kJSCreateClosure:
case IrOpcode::kJSCreateFunctionContext:
case IrOpcode::kJSCreateGeneratorObject:
case IrOpcode::kJSCreateCatchContext:
case IrOpcode::kJSCreateWithContext:
case IrOpcode::kJSDebugger:
case IrOpcode::kJSDeleteProperty:
case IrOpcode::kJSGeneratorStore:
case IrOpcode::kJSHasProperty:
case IrOpcode::kJSLoadContext:
case IrOpcode::kJSLoadModule:
case IrOpcode::kJSLoadNamed:
case IrOpcode::kJSLoadProperty:
case IrOpcode::kJSStoreContext:
case IrOpcode::kJSStoreDataPropertyInLiteral:
case IrOpcode::kJSStoreInArrayLiteral:
case IrOpcode::kJSStoreModule:
case IrOpcode::kJSStoreNamed:
case IrOpcode::kJSStoreNamedOwn:
case IrOpcode::kJSStoreProperty:
return true;
case IrOpcode::kJSCreateArrayIterator:
case IrOpcode::kJSCreateBoundFunction:
case IrOpcode::kJSCreateCollectionIterator:
case IrOpcode::kJSCreateIterResultObject:
case IrOpcode::kJSCreateStringIterator:
case IrOpcode::kJSCreateKeyValueArray:
case IrOpcode::kJSCreateObject:
case IrOpcode::kJSCreatePromise:
case IrOpcode::kJSCreateTypedArray:
case IrOpcode::kJSCreateArray:
case IrOpcode::kJSFulfillPromise:
case IrOpcode::kJSObjectIsArray:
case IrOpcode::kJSPerformPromiseThen:
case IrOpcode::kJSPromiseResolve:
case IrOpcode::kJSRegExpTest:
case IrOpcode::kJSRejectPromise:
case IrOpcode::kJSResolvePromise:
// These operators aren't introduced by BytecodeGraphBuilder and
// thus we don't bother checking them. If you ever introduce one
// of these early in the BytecodeGraphBuilder make sure to check
// whether they are context-sensitive.
break;
#define CASE(Name) case IrOpcode::k##Name:
// Non-JavaScript operators don't have a notion of "context"
COMMON_OP_LIST(CASE)
CONTROL_OP_LIST(CASE)
MACHINE_OP_LIST(CASE)
MACHINE_SIMD_OP_LIST(CASE)
SIMPLIFIED_OP_LIST(CASE)
break;
#undef CASE
}
UNREACHABLE();
}
// static
bool OperatorProperties::HasFrameStateInput(const Operator* op) {
......
......@@ -22,6 +22,8 @@ class V8_EXPORT_PRIVATE OperatorProperties final {
return HasContextInput(op) ? 1 : 0;
}
static bool NeedsExactContext(const Operator* op);
static bool HasFrameStateInput(const Operator* op);
static int GetFrameStateInputCount(const Operator* op) {
return HasFrameStateInput(op) ? 1 : 0;
......
......@@ -96,6 +96,44 @@ void InitializeIntrinsicFunctionNames() {
} // namespace
bool Runtime::NeedsExactContext(FunctionId id) {
switch (id) {
case Runtime::kAddPrivateField:
case Runtime::kCopyDataProperties:
case Runtime::kCreateDataProperty:
case Runtime::kCreatePrivateFieldSymbol:
case Runtime::kReThrow:
case Runtime::kThrow:
case Runtime::kThrowApplyNonFunction:
case Runtime::kThrowCalledNonCallable:
case Runtime::kThrowConstAssignError:
case Runtime::kThrowConstructorNonCallableError:
case Runtime::kThrowConstructedNonConstructable:
case Runtime::kThrowConstructorReturnedNonObject:
case Runtime::kThrowInvalidStringLength:
case Runtime::kThrowInvalidTypedArrayAlignment:
case Runtime::kThrowIteratorError:
case Runtime::kThrowIteratorResultNotAnObject:
case Runtime::kThrowNotConstructor:
case Runtime::kThrowRangeError:
case Runtime::kThrowReferenceError:
case Runtime::kThrowStackOverflow:
case Runtime::kThrowStaticPrototypeError:
case Runtime::kThrowSuperAlreadyCalledError:
case Runtime::kThrowSuperNotCalled:
case Runtime::kThrowSymbolAsyncIteratorInvalid:
case Runtime::kThrowSymbolIteratorInvalid:
case Runtime::kThrowThrowMethodMissing:
case Runtime::kThrowTypeError:
case Runtime::kThrowUnsupportedSuperError:
case Runtime::kThrowWasmError:
case Runtime::kThrowWasmStackOverflow:
return false;
default:
return true;
}
}
bool Runtime::IsNonReturning(FunctionId id) {
switch (id) {
case Runtime::kThrowUnsupportedSuperError:
......
......@@ -661,6 +661,11 @@ class Runtime : public AllStatic {
static const int kNotFound = -1;
// Checks whether the runtime function with the given {id} depends on the
// "current context", i.e. because it does scoped lookups, or whether it's
// fine to just pass any context within the same "native context".
static bool NeedsExactContext(FunctionId id);
// Checks whether the runtime function with the given {id} never returns
// to it's caller normally, i.e. whether it'll always raise an exception.
// More specifically: The C++ implementation returns the Heap::exception
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment