Commit ae6a47ba authored by Jakob Gruber's avatar Jakob Gruber Committed by Commit Bot

[coverage] Reduce IncBlockCounter overhead

When collecting JS block coverage, we track block execution counts on
so-called CoverageInfo objects. Generated bytecode and native code
contains inlined snippets of code to increment the appropriate
counters.

These used to be implemented as calls to the IncBlockCounter runtime
function. Each call incurred the entire CEntry overhead.

This CL reduces that overhead by moving logic over into a new
IncBlockCounter TFS builtin. The builtin is called directly from
bytecode, and lowered to the same builtin call for optimized code.

Drive-by: Tweak CoverageInfo layout to generate faster code.

Tbr: jarin@chromium.org
Bug: v8:9149, v8:6000
Change-Id: I2d7cb0db649edf7c56b5ef5a4683d27b1c34605c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1571420Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Reviewed-by: 's avatarPeter Marshall <petermarshall@chromium.org>
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60981}
parent 2caf6d0a
...@@ -938,6 +938,7 @@ torque_files = [ ...@@ -938,6 +938,7 @@ torque_files = [
"src/builtins/collections.tq", "src/builtins/collections.tq",
"src/builtins/data-view.tq", "src/builtins/data-view.tq",
"src/builtins/extras-utils.tq", "src/builtins/extras-utils.tq",
"src/builtins/internal-coverage.tq",
"src/builtins/iterator.tq", "src/builtins/iterator.tq",
"src/builtins/object-fromentries.tq", "src/builtins/object-fromentries.tq",
"src/builtins/proxy.tq", "src/builtins/proxy.tq",
...@@ -988,6 +989,7 @@ torque_namespaces = [ ...@@ -988,6 +989,7 @@ torque_namespaces = [
"data-view", "data-view",
"extras-utils", "extras-utils",
"growable-fixed-array", "growable-fixed-array",
"internal-coverage",
"iterator", "iterator",
"object", "object",
"proxy", "proxy",
......
...@@ -340,7 +340,6 @@ extern class Script extends Struct { ...@@ -340,7 +340,6 @@ extern class Script extends Struct {
source_mapping_url: Object; source_mapping_url: Object;
host_defined_options: Object; host_defined_options: Object;
} }
type DebugInfo extends HeapObject;
extern class EmbedderDataArray extends HeapObject { length: Smi; } extern class EmbedderDataArray extends HeapObject { length: Smi; }
...@@ -1033,6 +1032,18 @@ extern class AccessorPair extends Struct { ...@@ -1033,6 +1032,18 @@ extern class AccessorPair extends Struct {
extern class BreakPoint extends Tuple2 {} extern class BreakPoint extends Tuple2 {}
extern class BreakPointInfo extends Tuple2 {} extern class BreakPointInfo extends Tuple2 {}
extern class CoverageInfo extends FixedArray {}
extern class DebugInfo extends Struct {
shared_function_info: HeapObject;
debugger_hints: Smi;
script: Undefined | Script;
original_bytecode_array: Undefined | BytecodeArray;
debug_bytecode_array: Undefined | BytecodeArray;
break_points: FixedArray;
flags: Smi;
coverage_info: CoverageInfo | Undefined;
}
extern class WasmModuleObject extends JSObject { extern class WasmModuleObject extends JSObject {
native_module: Foreign; native_module: Foreign;
...@@ -1654,6 +1665,21 @@ Cast<JSFunction>(implicit context: Context)(o: HeapObject): JSFunction ...@@ -1654,6 +1665,21 @@ Cast<JSFunction>(implicit context: Context)(o: HeapObject): JSFunction
goto CastError; goto CastError;
} }
extern macro IsDebugInfo(HeapObject): bool;
Cast<DebugInfo>(implicit context: Context)(o: HeapObject): DebugInfo
labels CastError {
if (IsDebugInfo(o)) return %RawDownCast<DebugInfo>(o);
goto CastError;
}
extern macro IsCoverageInfo(HeapObject): bool;
Cast<CoverageInfo>(implicit context: Context)(o: HeapObject): CoverageInfo
labels CastError {
// TODO(jgruber): Assign an instance type.
if (IsFixedArray(o)) return %RawDownCast<CoverageInfo>(o);
goto CastError;
}
extern macro AllocateHeapNumberWithValue(float64): HeapNumber; extern macro AllocateHeapNumberWithValue(float64): HeapNumber;
extern macro ChangeInt32ToTagged(int32): Number; extern macro ChangeInt32ToTagged(int32): Number;
extern macro ChangeUint32ToTagged(uint32): Number; extern macro ChangeUint32ToTagged(uint32): Number;
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include 'src/builtins/builtins-regexp-gen.h'
namespace internal_coverage {
const kHasCoverageInfo:
constexpr int31 generates 'DebugInfo::kHasCoverageInfo';
const kFirstSlotIndex:
constexpr int31 generates 'CoverageInfo::kFirstSlotIndex';
const kSlotBlockCountIndex:
constexpr int31 generates 'CoverageInfo::kSlotBlockCountIndex';
const kSlotIndexCountLog2:
constexpr int31 generates 'CoverageInfo::kSlotIndexCountLog2';
const kSlotIndexCountMask:
constexpr int31 generates 'CoverageInfo::kSlotIndexCountMask';
macro GetCoverageInfo(implicit context: Context)(function: JSFunction):
CoverageInfo labels IfNoCoverageInfo {
const shared: SharedFunctionInfo = function.shared_function_info;
const debugInfo = Cast<DebugInfo>(shared.script_or_debug_info)
otherwise goto IfNoCoverageInfo;
if ((debugInfo.flags & kHasCoverageInfo) == 0) goto IfNoCoverageInfo;
return UnsafeCast<CoverageInfo>(debugInfo.coverage_info);
}
macro SlotCount(coverageInfo: CoverageInfo): Smi {
assert(kFirstSlotIndex == 0); // Otherwise we'd have to consider it below.
assert(kFirstSlotIndex == (coverageInfo.length & kSlotIndexCountMask));
return coverageInfo.length >> kSlotIndexCountLog2;
}
macro FirstIndexForSlot(implicit context: Context)(slot: Smi): Smi {
assert(kFirstSlotIndex == 0); // Otherwise we'd have to consider it below.
return slot << kSlotIndexCountLog2;
}
macro IncrementBlockCount(implicit context: Context)(
coverageInfo: CoverageInfo, slot: Smi) {
assert(slot < SlotCount(coverageInfo));
const slotStart: Smi = FirstIndexForSlot(slot);
const index: Smi = slotStart + kSlotBlockCountIndex;
coverageInfo.objects[index] =
UnsafeCast<Smi>(coverageInfo.objects[index]) + 1;
}
builtin IncBlockCounter(implicit context: Context)(
function: JSFunction, coverageArraySlotIndex: Smi): Object {
// It's quite possible that a function contains IncBlockCounter bytecodes,
// but no coverage info exists. This happens e.g. by selecting the
// best-effort coverage collection mode, which triggers deletion of all
// coverage infos in order to avoid memory leaks.
const coverageInfo: CoverageInfo =
GetCoverageInfo(function) otherwise return Undefined;
IncrementBlockCount(coverageInfo, coverageArraySlotIndex);
return Undefined;
}
} // namespace internal_coverage
...@@ -5917,6 +5917,10 @@ TNode<BoolT> CodeStubAssembler::IsCallableMap(SloppyTNode<Map> map) { ...@@ -5917,6 +5917,10 @@ TNode<BoolT> CodeStubAssembler::IsCallableMap(SloppyTNode<Map> map) {
return IsSetWord32<Map::IsCallableBit>(LoadMapBitField(map)); return IsSetWord32<Map::IsCallableBit>(LoadMapBitField(map));
} }
TNode<BoolT> CodeStubAssembler::IsDebugInfo(TNode<HeapObject> object) {
return HasInstanceType(object, DEBUG_INFO_TYPE);
}
TNode<BoolT> CodeStubAssembler::IsDeprecatedMap(SloppyTNode<Map> map) { TNode<BoolT> CodeStubAssembler::IsDeprecatedMap(SloppyTNode<Map> map) {
CSA_ASSERT(this, IsMap(map)); CSA_ASSERT(this, IsMap(map));
return IsSetWord32<Map::IsDeprecatedBit>(LoadMapBitField3(map)); return IsSetWord32<Map::IsDeprecatedBit>(LoadMapBitField3(map));
......
...@@ -2146,6 +2146,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler ...@@ -2146,6 +2146,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<BoolT> IsConsStringInstanceType(SloppyTNode<Int32T> instance_type); TNode<BoolT> IsConsStringInstanceType(SloppyTNode<Int32T> instance_type);
TNode<BoolT> IsConstructorMap(SloppyTNode<Map> map); TNode<BoolT> IsConstructorMap(SloppyTNode<Map> map);
TNode<BoolT> IsConstructor(SloppyTNode<HeapObject> object); TNode<BoolT> IsConstructor(SloppyTNode<HeapObject> object);
TNode<BoolT> IsDebugInfo(TNode<HeapObject> object);
TNode<BoolT> IsDeprecatedMap(SloppyTNode<Map> map); TNode<BoolT> IsDeprecatedMap(SloppyTNode<Map> map);
TNode<BoolT> IsNameDictionary(SloppyTNode<HeapObject> object); TNode<BoolT> IsNameDictionary(SloppyTNode<HeapObject> object);
TNode<BoolT> IsGlobalDictionary(SloppyTNode<HeapObject> object); TNode<BoolT> IsGlobalDictionary(SloppyTNode<HeapObject> object);
......
...@@ -2817,7 +2817,9 @@ void BytecodeGraphBuilder::VisitIncBlockCounter() { ...@@ -2817,7 +2817,9 @@ void BytecodeGraphBuilder::VisitIncBlockCounter() {
Node* coverage_array_slot = Node* coverage_array_slot =
jsgraph()->Constant(bytecode_iterator().GetIndexOperand(0)); jsgraph()->Constant(bytecode_iterator().GetIndexOperand(0));
const Operator* op = javascript()->CallRuntime(Runtime::kIncBlockCounter); // Lowered by js-intrinsic-lowering to call Builtins::kIncBlockCounter.
const Operator* op =
javascript()->CallRuntime(Runtime::kInlineIncBlockCounter);
NewNode(op, closure, coverage_array_slot); NewNode(op, closure, coverage_array_slot);
} }
......
...@@ -76,6 +76,8 @@ Reduction JSIntrinsicLowering::Reduce(Node* node) { ...@@ -76,6 +76,8 @@ Reduction JSIntrinsicLowering::Reduce(Node* node) {
return ReduceToString(node); return ReduceToString(node);
case Runtime::kInlineCall: case Runtime::kInlineCall:
return ReduceCall(node); return ReduceCall(node);
case Runtime::kInlineIncBlockCounter:
return ReduceIncBlockCounter(node);
default: default:
break; break;
} }
...@@ -301,6 +303,14 @@ Reduction JSIntrinsicLowering::ReduceCall(Node* node) { ...@@ -301,6 +303,14 @@ Reduction JSIntrinsicLowering::ReduceCall(Node* node) {
return Changed(node); return Changed(node);
} }
Reduction JSIntrinsicLowering::ReduceIncBlockCounter(Node* node) {
DCHECK(!Linkage::NeedsFrameStateInput(Runtime::kIncBlockCounter));
DCHECK(!Linkage::NeedsFrameStateInput(Runtime::kInlineIncBlockCounter));
return Change(node,
Builtins::CallableFor(isolate(), Builtins::kIncBlockCounter), 0,
kDoesNotNeedFrameState);
}
Reduction JSIntrinsicLowering::Change(Node* node, const Operator* op, Node* a, Reduction JSIntrinsicLowering::Change(Node* node, const Operator* op, Node* a,
Node* b) { Node* b) {
RelaxControls(node); RelaxControls(node);
...@@ -336,19 +346,21 @@ Reduction JSIntrinsicLowering::Change(Node* node, const Operator* op, Node* a, ...@@ -336,19 +346,21 @@ Reduction JSIntrinsicLowering::Change(Node* node, const Operator* op, Node* a,
return Changed(node); return Changed(node);
} }
Reduction JSIntrinsicLowering::Change(Node* node, Callable const& callable, Reduction JSIntrinsicLowering::Change(Node* node, Callable const& callable,
int stack_parameter_count) { int stack_parameter_count,
enum FrameStateFlag frame_state_flag) {
CallDescriptor::Flags flags = frame_state_flag == kNeedsFrameState
? CallDescriptor::kNeedsFrameState
: CallDescriptor::kNoFlags;
auto call_descriptor = Linkage::GetStubCallDescriptor( auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(), stack_parameter_count, graph()->zone(), callable.descriptor(), stack_parameter_count, flags,
CallDescriptor::kNeedsFrameState, node->op()->properties()); node->op()->properties());
node->InsertInput(graph()->zone(), 0, node->InsertInput(graph()->zone(), 0,
jsgraph()->HeapConstant(callable.code())); jsgraph()->HeapConstant(callable.code()));
NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); NodeProperties::ChangeOp(node, common()->Call(call_descriptor));
return Changed(node); return Changed(node);
} }
Graph* JSIntrinsicLowering::graph() const { return jsgraph()->graph(); } Graph* JSIntrinsicLowering::graph() const { return jsgraph()->graph(); }
......
...@@ -61,14 +61,21 @@ class V8_EXPORT_PRIVATE JSIntrinsicLowering final ...@@ -61,14 +61,21 @@ class V8_EXPORT_PRIVATE JSIntrinsicLowering final
Reduction ReduceToObject(Node* node); Reduction ReduceToObject(Node* node);
Reduction ReduceToString(Node* node); Reduction ReduceToString(Node* node);
Reduction ReduceCall(Node* node); Reduction ReduceCall(Node* node);
Reduction ReduceIncBlockCounter(Node* node);
Reduction Change(Node* node, const Operator* op); Reduction Change(Node* node, const Operator* op);
Reduction Change(Node* node, const Operator* op, Node* a, Node* b); Reduction Change(Node* node, const Operator* op, Node* a, Node* b);
Reduction Change(Node* node, const Operator* op, Node* a, Node* b, Node* c); Reduction Change(Node* node, const Operator* op, Node* a, Node* b, Node* c);
Reduction Change(Node* node, const Operator* op, Node* a, Node* b, Node* c, Reduction Change(Node* node, const Operator* op, Node* a, Node* b, Node* c,
Node* d); Node* d);
enum FrameStateFlag {
kNeedsFrameState,
kDoesNotNeedFrameState,
};
Reduction Change(Node* node, Callable const& callable, Reduction Change(Node* node, Callable const& callable,
int stack_parameter_count); int stack_parameter_count,
enum FrameStateFlag frame_state_flag = kNeedsFrameState);
Graph* graph() const; Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; } JSGraph* jsgraph() const { return jsgraph_; }
......
...@@ -197,6 +197,7 @@ bool Linkage::NeedsFrameStateInput(Runtime::FunctionId function) { ...@@ -197,6 +197,7 @@ bool Linkage::NeedsFrameStateInput(Runtime::FunctionId function) {
// Some inline intrinsics are also safe to call without a FrameState. // Some inline intrinsics are also safe to call without a FrameState.
case Runtime::kInlineCreateIterResultObject: case Runtime::kInlineCreateIterResultObject:
case Runtime::kInlineIncBlockCounter:
case Runtime::kInlineGeneratorClose: case Runtime::kInlineGeneratorClose:
case Runtime::kInlineGeneratorGetResumeMode: case Runtime::kInlineGeneratorGetResumeMode:
case Runtime::kInlineCreateJSGeneratorObject: case Runtime::kInlineCreateJSGeneratorObject:
......
...@@ -2961,7 +2961,8 @@ IGNITION_HANDLER(IncBlockCounter, InterpreterAssembler) { ...@@ -2961,7 +2961,8 @@ IGNITION_HANDLER(IncBlockCounter, InterpreterAssembler) {
Node* coverage_array_slot = BytecodeOperandIdxSmi(0); Node* coverage_array_slot = BytecodeOperandIdxSmi(0);
Node* context = GetContext(); Node* context = GetContext();
CallRuntime(Runtime::kIncBlockCounter, context, closure, coverage_array_slot); CallBuiltin(Builtins::kIncBlockCounter, context, closure,
coverage_array_slot);
Dispatch(); Dispatch();
} }
......
...@@ -37,7 +37,7 @@ ACCESSORS(DebugInfo, script, Object, kScriptOffset) ...@@ -37,7 +37,7 @@ ACCESSORS(DebugInfo, script, Object, kScriptOffset)
ACCESSORS(DebugInfo, original_bytecode_array, Object, ACCESSORS(DebugInfo, original_bytecode_array, Object,
kOriginalBytecodeArrayOffset) kOriginalBytecodeArrayOffset)
ACCESSORS(DebugInfo, debug_bytecode_array, Object, kDebugBytecodeArrayOffset) ACCESSORS(DebugInfo, debug_bytecode_array, Object, kDebugBytecodeArrayOffset)
ACCESSORS(DebugInfo, break_points, FixedArray, kBreakPointsStateOffset) ACCESSORS(DebugInfo, break_points, FixedArray, kBreakPointsOffset)
ACCESSORS(DebugInfo, coverage_info, Object, kCoverageInfoOffset) ACCESSORS(DebugInfo, coverage_info, Object, kCoverageInfoOffset)
BIT_FIELD_ACCESSORS(DebugInfo, debugger_hints, side_effect_state, BIT_FIELD_ACCESSORS(DebugInfo, debugger_hints, side_effect_state,
......
...@@ -168,21 +168,9 @@ class DebugInfo : public Struct { ...@@ -168,21 +168,9 @@ class DebugInfo : public Struct {
DECL_PRINTER(DebugInfo) DECL_PRINTER(DebugInfo)
DECL_VERIFIER(DebugInfo) DECL_VERIFIER(DebugInfo)
// Layout description. // Layout description.
#define DEBUG_INFO_FIELDS(V) \ DEFINE_FIELD_OFFSET_CONSTANTS(Struct::kHeaderSize,
V(kSharedFunctionInfoOffset, kTaggedSize) \ TORQUE_GENERATED_DEBUG_INFO_FIELDS)
V(kDebuggerHintsOffset, kTaggedSize) \
V(kScriptOffset, kTaggedSize) \
V(kOriginalBytecodeArrayOffset, kTaggedSize) \
V(kDebugBytecodeArrayOffset, kTaggedSize) \
V(kBreakPointsStateOffset, kTaggedSize) \
V(kFlagsOffset, kTaggedSize) \
V(kCoverageInfoOffset, kTaggedSize) \
/* Total size. */ \
V(kSize, 0)
DEFINE_FIELD_OFFSET_CONSTANTS(Struct::kHeaderSize, DEBUG_INFO_FIELDS)
#undef DEBUG_INFO_FIELDS
static const int kEstimatedNofBreakPointsInFunction = 4; static const int kEstimatedNofBreakPointsInFunction = 4;
...@@ -247,11 +235,6 @@ class CoverageInfo : public FixedArray { ...@@ -247,11 +235,6 @@ class CoverageInfo : public FixedArray {
// Print debug info. // Print debug info.
void Print(std::unique_ptr<char[]> function_name); void Print(std::unique_ptr<char[]> function_name);
private:
static int FirstIndexForSlot(int slot_index) {
return kFirstSlotIndex + slot_index * kSlotIndexCount;
}
static const int kFirstSlotIndex = 0; static const int kFirstSlotIndex = 0;
// Each slot is assigned a group of indices starting at kFirstSlotIndex. // Each slot is assigned a group of indices starting at kFirstSlotIndex.
...@@ -259,7 +242,17 @@ class CoverageInfo : public FixedArray { ...@@ -259,7 +242,17 @@ class CoverageInfo : public FixedArray {
static const int kSlotStartSourcePositionIndex = 0; static const int kSlotStartSourcePositionIndex = 0;
static const int kSlotEndSourcePositionIndex = 1; static const int kSlotEndSourcePositionIndex = 1;
static const int kSlotBlockCountIndex = 2; static const int kSlotBlockCountIndex = 2;
static const int kSlotIndexCount = 3; static const int kSlotPaddingIndex = 3; // Padding to make the index count 4.
static const int kSlotIndexCount = 4;
static const int kSlotIndexCountLog2 = 2;
static const int kSlotIndexCountMask = (kSlotIndexCount - 1);
STATIC_ASSERT(1 << kSlotIndexCountLog2 == kSlotIndexCount);
private:
static int FirstIndexForSlot(int slot_index) {
return kFirstSlotIndex + slot_index * kSlotIndexCount;
}
OBJECT_CONSTRUCTORS(CoverageInfo, FixedArray); OBJECT_CONSTRUCTORS(CoverageInfo, FixedArray);
}; };
......
...@@ -737,23 +737,7 @@ RUNTIME_FUNCTION(Runtime_DebugToggleBlockCoverage) { ...@@ -737,23 +737,7 @@ RUNTIME_FUNCTION(Runtime_DebugToggleBlockCoverage) {
} }
RUNTIME_FUNCTION(Runtime_IncBlockCounter) { RUNTIME_FUNCTION(Runtime_IncBlockCounter) {
SealHandleScope scope(isolate); UNREACHABLE(); // Never called. See the IncBlockCounter builtin instead.
DCHECK_EQ(2, args.length());
CONVERT_ARG_CHECKED(JSFunction, function, 0);
CONVERT_SMI_ARG_CHECKED(coverage_array_slot_index, 1);
// It's quite possible that a function contains IncBlockCounter bytecodes, but
// no coverage info exists. This happens e.g. by selecting the best-effort
// coverage collection mode, which triggers deletion of all coverage infos in
// order to avoid memory leaks.
SharedFunctionInfo shared = function->shared();
if (shared->HasCoverageInfo()) {
CoverageInfo coverage_info = shared->GetCoverageInfo();
coverage_info->IncrementBlockCount(coverage_array_slot_index);
}
return ReadOnlyRoots(isolate).undefined_value();
} }
RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionEntered) { RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionEntered) {
......
...@@ -137,7 +137,7 @@ namespace internal { ...@@ -137,7 +137,7 @@ namespace internal {
F(GetGeneratorScopeDetails, 2, 1) \ F(GetGeneratorScopeDetails, 2, 1) \
F(GetHeapUsage, 0, 1) \ F(GetHeapUsage, 0, 1) \
F(HandleDebuggerStatement, 0, 1) \ F(HandleDebuggerStatement, 0, 1) \
F(IncBlockCounter, 2, 1) \ I(IncBlockCounter, 2, 1) \
F(IsBreakOnException, 1, 1) \ F(IsBreakOnException, 1, 1) \
F(ScheduleBreak, 0, 1) \ F(ScheduleBreak, 0, 1) \
F(ScriptLocationFromLine2, 4, 1) \ F(ScriptLocationFromLine2, 4, 1) \
......
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