Commit 4e9f6513 authored by Paolo Severini's avatar Paolo Severini Committed by Commit Bot

[test][turbofan] Add %ObserveNode intrinsic for node-specific tests

(Initially copied from nicohartmann@ CL
https://chromium-review.googlesource.com/c/v8/v8/+/2135631)

This CL adds a new intrinsic %ObserveNode(expr) which has noop semantics
but triggers the new NodeObserver set on the OptimizedCompilationInfo
when the node generated for expr is created or changed in any phase
(until EffectControlLinearization).

This provides the infrastructure to write reasonable unit tests that
check for the construction of or lowering to specific nodes (e.g.
depending on feedback).

When %ObserveNode(expr) is used an object of class ObserveNodeManager is
registered to every Reducer/GraphReducer and is notified by the Reducer
with all node changes. The same logic is added to classes
SimplifiedLowering/RepresentationSelector, which do not inherit from
class Reducer.

Observed Node modifications currently are:
 * The Node Operator
 * The Node type
 * Node replacements

A first use case (cctest/test-sloppy-equality.cc) is included in this CL.

Change-Id: Idc5a5e38af8b1d9a2ec5021bf821c4e4e1406220
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2555219
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Reviewed-by: 's avatarGeorg Neis <neis@chromium.org>
Reviewed-by: 's avatarNico Hartmann <nicohartmann@chromium.org>
Cr-Commit-Position: refs/heads/master@{#72331}
parent 80b97562
......@@ -2301,6 +2301,8 @@ v8_compiler_sources = [
"src/compiler/node-marker.h",
"src/compiler/node-matchers.cc",
"src/compiler/node-matchers.h",
"src/compiler/node-observer.cc",
"src/compiler/node-observer.h",
"src/compiler/node-origin-table.cc",
"src/compiler/node-origin-table.h",
"src/compiler/node-properties.cc",
......
......@@ -34,6 +34,10 @@ class JavaScriptFrame;
class JSGlobalObject;
class Zone;
namespace compiler {
class NodeObserver;
}
namespace wasm {
struct WasmCompilationResult;
} // namespace wasm
......@@ -121,6 +125,11 @@ class V8_EXPORT_PRIVATE OptimizedCompilationInfo final {
void set_builtin_index(int32_t index) { builtin_index_ = index; }
BytecodeOffset osr_offset() const { return osr_offset_; }
JavaScriptFrame* osr_frame() const { return osr_frame_; }
void SetNodeObserver(compiler::NodeObserver* observer) {
DCHECK_NULL(node_observer_);
node_observer_ = observer;
}
compiler::NodeObserver* node_observer() const { return node_observer_; }
void SetPoisoningMitigationLevel(PoisoningMitigationLevel poisoning_level) {
poisoning_level_ = poisoning_level;
......@@ -284,6 +293,8 @@ class V8_EXPORT_PRIVATE OptimizedCompilationInfo final {
// OptimizedCompilationInfo allocates.
Zone* const zone_;
compiler::NodeObserver* node_observer_ = nullptr;
BailoutReason bailout_reason_ = BailoutReason::kNoReason;
InlinedFunctionList inlined_functions_;
......
......@@ -15,6 +15,7 @@
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-observer.h"
#include "src/compiler/operator-properties.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/state-values-utils.h"
......@@ -42,7 +43,8 @@ class BytecodeGraphBuilder {
CallFrequency const& invocation_frequency,
SourcePositionTable* source_positions, int inlining_id,
CodeKind code_kind, BytecodeGraphBuilderFlags flags,
TickCounter* tick_counter);
TickCounter* tick_counter,
ObserveNodeInfo const& observe_node_info);
BytecodeGraphBuilder(const BytecodeGraphBuilder&) = delete;
BytecodeGraphBuilder& operator=(const BytecodeGraphBuilder&) = delete;
......@@ -530,6 +532,8 @@ class BytecodeGraphBuilder {
TickCounter* const tick_counter_;
ObserveNodeInfo const observe_node_info_;
static constexpr int kBinaryOperationHintIndex = 1;
static constexpr int kBinaryOperationSmiHintIndex = 1;
static constexpr int kCompareOperationHintIndex = 1;
......@@ -1041,7 +1045,8 @@ BytecodeGraphBuilder::BytecodeGraphBuilder(
FeedbackCellRef const& feedback_cell, BytecodeOffset osr_offset,
JSGraph* jsgraph, CallFrequency const& invocation_frequency,
SourcePositionTable* source_positions, int inlining_id, CodeKind code_kind,
BytecodeGraphBuilderFlags flags, TickCounter* tick_counter)
BytecodeGraphBuilderFlags flags, TickCounter* tick_counter,
ObserveNodeInfo const& observe_node_info)
: broker_(broker),
local_zone_(local_zone),
jsgraph_(jsgraph),
......@@ -1087,7 +1092,8 @@ BytecodeGraphBuilder::BytecodeGraphBuilder(
state_values_cache_(jsgraph),
source_positions_(source_positions),
start_position_(shared_info.StartPosition(), inlining_id),
tick_counter_(tick_counter) {}
tick_counter_(tick_counter),
observe_node_info_(observe_node_info) {}
Node* BytecodeGraphBuilder::GetFunctionClosure() {
if (!function_closure_.is_set()) {
......@@ -2742,16 +2748,25 @@ void BytecodeGraphBuilder::VisitCallRuntime() {
interpreter::Register receiver = bytecode_iterator().GetRegisterOperand(1);
size_t reg_count = bytecode_iterator().GetRegisterCountOperand(2);
// Create node to perform the runtime call.
const Operator* call = javascript()->CallRuntime(function_id, reg_count);
Node* value = ProcessCallRuntimeArguments(call, receiver, reg_count);
environment()->BindAccumulator(value, Environment::kAttachFrameState);
// Connect to the end if {function_id} is non-returning.
if (Runtime::IsNonReturning(function_id)) {
// TODO(7099): Investigate if we need LoopExit node here.
Node* control = NewNode(common()->Throw());
MergeControlToLeaveFunction(control);
// Handle %ObserveNode here (rather than in JSIntrinsicLowering) to observe
// the node as early as possible.
if (function_id == Runtime::FunctionId::kObserveNode) {
DCHECK_EQ(1, reg_count);
Node* value = environment()->LookupRegister(receiver);
observe_node_info_.StartObserving(value);
environment()->BindAccumulator(value);
} else {
// Create node to perform the runtime call.
const Operator* call = javascript()->CallRuntime(function_id, reg_count);
Node* value = ProcessCallRuntimeArguments(call, receiver, reg_count);
environment()->BindAccumulator(value, Environment::kAttachFrameState);
// Connect to the end if {function_id} is non-returning.
if (Runtime::IsNonReturning(function_id)) {
// TODO(7099): Investigate if we need LoopExit node here.
Node* control = NewNode(common()->Throw());
MergeControlToLeaveFunction(control);
}
}
}
......@@ -4559,14 +4574,16 @@ void BuildGraphFromBytecode(JSHeapBroker* broker, Zone* local_zone,
SourcePositionTable* source_positions,
int inlining_id, CodeKind code_kind,
BytecodeGraphBuilderFlags flags,
TickCounter* tick_counter) {
TickCounter* tick_counter,
ObserveNodeInfo const& observe_node_info) {
DCHECK(broker->IsSerializedForCompilation(
shared_info, feedback_cell.value()->AsFeedbackVector()));
DCHECK(feedback_cell.value()->AsFeedbackVector().serialized());
BytecodeGraphBuilder builder(
broker, local_zone, broker->target_native_context(), shared_info,
feedback_cell, osr_offset, jsgraph, invocation_frequency,
source_positions, inlining_id, code_kind, flags, tick_counter);
source_positions, inlining_id, code_kind, flags, tick_counter,
observe_node_info);
builder.CreateGraph();
}
......
......@@ -7,6 +7,7 @@
#include "src/compiler/js-operator.h"
#include "src/compiler/js-type-hint-lowering.h"
#include "src/compiler/node-observer.h"
#include "src/handles/handles.h"
#include "src/objects/code-kind.h"
#include "src/utils/utils.h"
......@@ -25,6 +26,7 @@ class Zone;
namespace compiler {
class JSGraph;
class NodeObserver;
class SourcePositionTable;
enum class BytecodeGraphBuilderFlag : uint8_t {
......@@ -47,7 +49,8 @@ void BuildGraphFromBytecode(JSHeapBroker* broker, Zone* local_zone,
SourcePositionTable* source_positions,
int inlining_id, CodeKind code_kind,
BytecodeGraphBuilderFlags flags,
TickCounter* tick_counter);
TickCounter* tick_counter,
ObserveNodeInfo const& observe_node_info = {});
} // namespace compiler
} // namespace internal
......
......@@ -10,6 +10,7 @@
#include "src/codegen/tick-counter.h"
#include "src/compiler/graph.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/node-observer.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/node.h"
#include "src/compiler/verifier.h"
......@@ -28,8 +29,19 @@ enum class GraphReducer::State : uint8_t {
void Reducer::Finalize() {}
Reduction Reducer::Reduce(Node* node,
ObserveNodeManager* observe_node_manager) {
Reduction reduction = Reduce(node);
if (V8_UNLIKELY(observe_node_manager && reduction.Changed())) {
observe_node_manager->OnNodeChanged(reducer_name(), node,
reduction.replacement());
}
return reduction;
}
GraphReducer::GraphReducer(Zone* zone, Graph* graph, TickCounter* tick_counter,
JSHeapBroker* broker, Node* dead)
JSHeapBroker* broker, Node* dead,
ObserveNodeManager* observe_node_manager)
: graph_(graph),
dead_(dead),
state_(graph, 4),
......@@ -37,7 +49,8 @@ GraphReducer::GraphReducer(Zone* zone, Graph* graph, TickCounter* tick_counter,
revisit_(zone),
stack_(zone),
tick_counter_(tick_counter),
broker_(broker) {
broker_(broker),
observe_node_manager_(observe_node_manager) {
if (dead != nullptr) {
NodeProperties::SetType(dead_, Type::None());
}
......@@ -89,7 +102,7 @@ Reduction GraphReducer::Reduce(Node* const node) {
for (auto i = reducers_.begin(); i != reducers_.end();) {
if (i != skip) {
tick_counter_->TickAndMaybeEnterSafepoint();
Reduction reduction = (*i)->Reduce(node);
Reduction reduction = (*i)->Reduce(node, observe_node_manager_);
if (!reduction.Changed()) {
// No change from this reducer.
} else if (reduction.replacement() == node) {
......
......@@ -20,6 +20,7 @@ namespace compiler {
class Graph;
class JSHeapBroker;
class Node;
class ObserveNodeManager;
// NodeIds are identifying numbers for nodes that can be used to index auxiliary
// out-of-line data associated with each node.
......@@ -58,7 +59,7 @@ class V8_EXPORT_PRIVATE Reducer {
virtual const char* reducer_name() const = 0;
// Try to reduce a node if possible.
virtual Reduction Reduce(Node* node) = 0;
Reduction Reduce(Node* node, ObserveNodeManager* observe_node_manager);
// Invoked by the {GraphReducer} when all nodes are done. Can be used to
// do additional reductions at the end, which in turn can cause a new round
......@@ -69,6 +70,9 @@ class V8_EXPORT_PRIVATE Reducer {
static Reduction NoChange() { return Reduction(); }
static Reduction Replace(Node* node) { return Reduction(node); }
static Reduction Changed(Node* node) { return Reduction(node); }
private:
virtual Reduction Reduce(Node* node) = 0;
};
......@@ -136,7 +140,8 @@ class V8_EXPORT_PRIVATE GraphReducer
: public NON_EXPORTED_BASE(AdvancedReducer::Editor) {
public:
GraphReducer(Zone* zone, Graph* graph, TickCounter* tick_counter,
JSHeapBroker* broker, Node* dead = nullptr);
JSHeapBroker* broker, Node* dead = nullptr,
ObserveNodeManager* observe_node_manager = nullptr);
~GraphReducer() override;
GraphReducer(const GraphReducer&) = delete;
......@@ -193,6 +198,7 @@ class V8_EXPORT_PRIVATE GraphReducer
ZoneStack<NodeState> stack_;
TickCounter* const tick_counter_;
JSHeapBroker* const broker_;
ObserveNodeManager* const observe_node_manager_;
};
} // namespace compiler
......
// Copyright 2021 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/compiler/node-observer.h"
#include "src/compiler/node-properties.h"
namespace v8 {
namespace internal {
namespace compiler {
ObservableNodeState::ObservableNodeState(const Node* node, Zone* zone)
: id_(node->id()),
op_(node->op()),
type_(NodeProperties::GetTypeOrAny(node)) {}
void ObserveNodeManager::StartObserving(Node* node, NodeObserver* observer) {
DCHECK_NOT_NULL(node);
DCHECK_NOT_NULL(observer);
DCHECK(observations_.find(node->id()) == observations_.end());
NodeObserver::Observation observation = observer->OnNodeCreated(node);
if (observation == NodeObserver::Observation::kContinue) {
observations_[node->id()] =
zone_->New<NodeObservation>(observer, node, zone_);
} else {
DCHECK_EQ(observation, NodeObserver::Observation::kStop);
}
}
void ObserveNodeManager::OnNodeChanged(const char* reducer_name,
const Node* old_node,
const Node* new_node) {
const auto it = observations_.find(old_node->id());
if (it == observations_.end()) return;
ObservableNodeState new_state{new_node, zone_};
NodeObservation* observation = it->second;
if (observation->state == new_state) return;
ObservableNodeState old_state = observation->state;
observation->state = new_state;
NodeObserver::Observation result =
observation->observer->OnNodeChanged(reducer_name, new_node, old_state);
if (result == NodeObserver::Observation::kStop) {
observations_.erase(old_node->id());
} else {
DCHECK_EQ(result, NodeObserver::Observation::kContinue);
if (old_node != new_node) {
observations_.erase(old_node->id());
observations_[new_node->id()] = observation;
}
}
}
} // namespace compiler
} // namespace internal
} // namespace v8
// Copyright 2021 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.
// This file declares the implementation of a new intrinsic %ObserveNode(expr),
// which has noop semantics but triggers the invocation of callbacks on a
// NodeObserver object. The NodeObserver is set on the OptimizedCompilationInfo
// and callbacks are called when the node generated for 'expr' is created or
// changed in any phase, until EffectControlLinearization.
//
// The modifications currently observed are changes to the observed Node
// operator and type and its replacement with another Node.
//
// This provides the infrastructure to write unit tests that check for the
// construction of or the lowering to specific nodes in the TurboFan graphs.
#ifndef V8_COMPILER_NODE_OBSERVER_H_
#define V8_COMPILER_NODE_OBSERVER_H_
#include "src/compiler/node.h"
#include "src/compiler/operator.h"
#include "src/zone/zone.h"
namespace v8 {
namespace internal {
namespace compiler {
class Node;
class Operator;
class ObservableNodeState {
public:
ObservableNodeState(const Node* node, Zone* zone);
uint32_t id() const { return id_; }
const Operator* op() const { return op_; }
int16_t opcode() const { return op_->opcode(); }
Type type() const { return type_; }
private:
uint32_t id_;
const Operator* op_;
Type type_;
};
inline bool operator==(const ObservableNodeState& lhs,
const ObservableNodeState& rhs) {
return lhs.id() == rhs.id() && lhs.op() == rhs.op() &&
lhs.type() == rhs.type();
}
inline bool operator!=(const ObservableNodeState& lhs,
const ObservableNodeState& rhs) {
return !operator==(lhs, rhs);
}
class NodeObserver : public ZoneObject {
public:
enum class Observation {
kContinue,
kStop,
};
NodeObserver() = default;
virtual ~NodeObserver() = 0;
NodeObserver(const NodeObserver&) = delete;
NodeObserver& operator=(const NodeObserver&) = delete;
virtual Observation OnNodeCreated(const Node* node) {
return Observation::kContinue;
}
virtual Observation OnNodeChanged(const char* reducer_name, const Node* node,
const ObservableNodeState& old_state) {
return Observation::kContinue;
}
};
inline NodeObserver::~NodeObserver() = default;
struct NodeObservation : public ZoneObject {
NodeObservation(NodeObserver* node_observer, const Node* node, Zone* zone)
: observer(node_observer), state(node, zone) {
DCHECK_NOT_NULL(node_observer);
}
NodeObserver* observer;
ObservableNodeState state;
};
class ObserveNodeManager : public ZoneObject {
public:
explicit ObserveNodeManager(Zone* zone) : zone_(zone), observations_(zone) {}
void StartObserving(Node* node, NodeObserver* observer);
void OnNodeChanged(const char* reducer_name, const Node* old_node,
const Node* new_node);
private:
Zone* zone_;
ZoneMap<NodeId, NodeObservation*> observations_;
};
struct ObserveNodeInfo {
ObserveNodeInfo() : observe_node_manager(nullptr), node_observer(nullptr) {}
ObserveNodeInfo(ObserveNodeManager* manager, NodeObserver* observer)
: observe_node_manager(manager), node_observer(observer) {}
void StartObserving(Node* node) const {
if (observe_node_manager) {
DCHECK_NOT_NULL(node_observer);
observe_node_manager->StartObserving(node, node_observer);
}
}
ObserveNodeManager* observe_node_manager;
NodeObserver* node_observer;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_NODE_OBSERVER_H_
......@@ -568,11 +568,10 @@ Node* NodeProperties::GetOuterContext(Node* node, size_t* depth) {
}
// static
Type NodeProperties::GetTypeOrAny(Node* node) {
Type NodeProperties::GetTypeOrAny(const Node* node) {
return IsTyped(node) ? node->type() : Type::Any();
}
// static
bool NodeProperties::AllValueInputsAreTyped(Node* node) {
int input_count = node->op()->ValueInputCount();
......
......@@ -21,7 +21,7 @@ class Operator;
class CommonOperatorBuilder;
// A facade that simplifies access to the different kinds of inputs to a node.
class V8_EXPORT_PRIVATE NodeProperties final {
class V8_EXPORT_PRIVATE NodeProperties {
public:
// ---------------------------------------------------------------------------
// Input layout.
......@@ -244,12 +244,12 @@ class V8_EXPORT_PRIVATE NodeProperties final {
// ---------------------------------------------------------------------------
// Type.
static bool IsTyped(Node* node) { return !node->type().IsInvalid(); }
static bool IsTyped(const Node* node) { return !node->type().IsInvalid(); }
static Type GetType(Node* node) {
DCHECK(IsTyped(node));
return node->type();
}
static Type GetTypeOrAny(Node* node);
static Type GetTypeOrAny(const Node* node);
static void SetType(Node* node, Type type) {
DCHECK(!type.IsInvalid());
node->set_type(type);
......
This diff is collapsed.
......@@ -38,7 +38,7 @@ void ScheduledMachineLowering::Run() {
Node* node = *instr;
Reduction reduction;
for (auto reducer : reducers_) {
reduction = reducer->Reduce(node);
reduction = reducer->Reduce(node, nullptr);
if (reduction.Changed()) break;
}
if (reduction.Changed()) {
......
This diff is collapsed.
......@@ -7,6 +7,7 @@
#include "src/compiler/js-graph.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/node.h"
#include "src/compiler/simplified-operator.h"
......@@ -19,6 +20,7 @@ namespace compiler {
// Forward declarations.
class NodeOriginTable;
class ObserveNodeManager;
class RepresentationChanger;
class RepresentationSelector;
class SourcePositionTable;
......@@ -30,7 +32,8 @@ class V8_EXPORT_PRIVATE SimplifiedLowering final {
SourcePositionTable* source_position,
NodeOriginTable* node_origins,
PoisoningMitigationLevel poisoning_level,
TickCounter* tick_counter, Linkage* linkage);
TickCounter* tick_counter, Linkage* linkage,
ObserveNodeManager* observe_node_manager = nullptr);
~SimplifiedLowering() = default;
void LowerAllNodes();
......@@ -50,6 +53,17 @@ class V8_EXPORT_PRIVATE SimplifiedLowering final {
void DoUnsigned32ToUint8Clamped(Node* node);
private:
// The purpose of this nested class is to hide method
// v8::internal::compiler::NodeProperties::ChangeOp which should not be
// directly used by code in SimplifiedLowering.
// SimplifiedLowering code should call SimplifiedLowering::ChangeOp instead,
// in order to notify the changes to ObserveNodeManager and support the
// %ObserveNode intrinsic.
class NodeProperties : public compiler::NodeProperties {
static void ChangeOp(Node* node, const Operator* new_op) { UNREACHABLE(); }
};
void ChangeOp(Node* node, const Operator* new_op);
JSGraph* const jsgraph_;
JSHeapBroker* broker_;
Zone* const zone_;
......@@ -74,6 +88,8 @@ class V8_EXPORT_PRIVATE SimplifiedLowering final {
TickCounter* const tick_counter_;
Linkage* const linkage_;
ObserveNodeManager* const observe_node_manager_;
Node* Float64Round(Node* const node);
Node* Float64Sign(Node* const node);
Node* Int32Abs(Node* const node);
......
......@@ -257,6 +257,14 @@ RUNTIME_FUNCTION(Runtime_NotifyDeoptimized) {
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_ObserveNode) {
// The %ObserveNode intrinsic only tracks the changes to an observed node in
// code compiled by TurboFan.
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, obj, 0);
return *obj;
}
static bool IsSuitableForOnStackReplacement(Isolate* isolate,
Handle<JSFunction> function) {
......
......@@ -111,6 +111,7 @@ namespace internal {
F(FunctionFirstExecution, 1, 1) \
F(InstantiateAsmJs, 4, 1) \
F(NotifyDeoptimized, 0, 1) \
F(ObserveNode, 1, 1) \
F(ResolvePossiblyDirectEval, 6, 1) \
F(TryInstallNCICode, 1, 1)
......
......@@ -89,6 +89,8 @@ v8_source_set("cctest_sources") {
"compiler/function-tester.cc",
"compiler/function-tester.h",
"compiler/graph-builder-tester.h",
"compiler/node-observer-tester.cc",
"compiler/node-observer-tester.h",
"compiler/serializer-tester.cc",
"compiler/serializer-tester.h",
"compiler/test-basic-block-profiler.cc",
......@@ -128,6 +130,7 @@ v8_source_set("cctest_sources") {
"compiler/test-run-tail-calls.cc",
"compiler/test-run-unwinding-info.cc",
"compiler/test-run-variables.cc",
"compiler/test-sloppy-equality.cc",
"compiler/value-helper.cc",
"compiler/value-helper.h",
"disasm-regex-helper.cc",
......
......@@ -546,6 +546,7 @@
'test-run-unwinding-info/*': [SKIP],
'test-run-variables/*': [SKIP],
'test-serialize/*': [SKIP],
'test-sloppy-equality/*' : [SKIP],
'test-torque/*': [SKIP],
'test-unwinder-code-pages/PCIsInV8_LargeCodeObject_CodePagesAPI': [SKIP],
......
// Copyright 2021 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 "test/cctest/compiler/node-observer-tester.h"
#include "src/api/api-inl.h"
#include "src/codegen/optimized-compilation-info.h"
#include "src/compiler/pipeline.h"
namespace v8 {
namespace internal {
namespace compiler {
void TestWithObserveNode::OptimizeFunctionWithObserver(
const char* function_name, NodeObserver* observer) {
DCHECK_NOT_NULL(function_name);
DCHECK_NOT_NULL(observer);
Local<Function> api_function = Local<Function>::Cast(
CcTest::global()
->Get(CcTest::isolate()->GetCurrentContext(), v8_str(function_name))
.ToLocalChecked());
Handle<JSFunction> function =
Handle<JSFunction>::cast(v8::Utils::OpenHandle(*api_function));
CHECK(function->shared().HasBytecodeArray());
Handle<SharedFunctionInfo> sfi(function->shared(), isolate_);
IsCompiledScope is_compiled_scope(sfi->is_compiled_scope(isolate_));
JSFunction::EnsureFeedbackVector(function, &is_compiled_scope);
OptimizedCompilationInfo compilation_info(main_zone(), isolate_, sfi,
function, CodeKind::TURBOFAN);
compilation_info.SetNodeObserver(observer);
compilation_info.ReopenHandlesInNewHandleScope(isolate_);
Handle<Code> code =
Pipeline::GenerateCodeForTesting(&compilation_info, isolate_)
.ToHandleChecked();
function->set_code(*code);
}
} // namespace compiler
} // namespace internal
} // namespace v8
// Copyright 2021 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.
#ifndef V8_CCTEST_COMPILER_NODEOBSERVER_TESTER_H_
#define V8_CCTEST_COMPILER_NODEOBSERVER_TESTER_H_
#include "src/compiler/node-observer.h"
#include "src/compiler/simplified-operator.h"
#include "src/objects/type-hints.h"
#include "test/cctest/cctest.h"
#include "test/common/wasm/flag-utils.h"
namespace v8 {
namespace internal {
namespace compiler {
// Test TurboFan compilation using the %ObserveNode intrinsic.
class TestWithObserveNode : public HandleAndZoneScope {
public:
explicit TestWithObserveNode(Isolate* isolate, const char* script)
: isolate_(isolate), script_(script) {
DCHECK_NOT_NULL(isolate_);
DCHECK_NOT_NULL(script_);
CompileRun(script_);
}
void OptimizeFunctionWithObserver(const char* function_name,
NodeObserver* observer);
private:
Isolate* isolate_;
const char* script_;
};
class CreationObserver : public NodeObserver {
public:
explicit CreationObserver(std::function<void(const Node*)> handler)
: handler_(handler) {
DCHECK(handler_);
}
Observation OnNodeCreated(const Node* node) override {
handler_(node);
return Observation::kStop;
}
private:
std::function<void(const Node*)> handler_;
};
class ModificationObserver : public NodeObserver {
public:
explicit ModificationObserver(
std::function<void(const Node*)> on_created_handler,
std::function<void(const Node*, const ObservableNodeState& old_state)>
on_changed_handler)
: on_created_handler_(on_created_handler),
on_changed_handler_(on_changed_handler) {
DCHECK(on_created_handler_);
DCHECK(on_changed_handler_);
}
Observation OnNodeCreated(const Node* node) override {
on_created_handler_(node);
return Observation::kContinue;
}
Observation OnNodeChanged(const char* reducer_name, const Node* node,
const ObservableNodeState& old_state) override {
on_changed_handler_(node, old_state);
return Observation::kContinue;
}
private:
std::function<void(const Node*)> on_created_handler_;
std::function<void(const Node*, const ObservableNodeState& old_state)>
on_changed_handler_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_CCTEST_COMPILER_NODEOBSERVER_TESTER_H_
// Copyright 2021 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 "test/cctest/compiler/node-observer-tester.h"
namespace v8 {
namespace internal {
namespace compiler {
struct TestCase {
TestCase(const char* l, const char* r, NodeObserver* observer)
: warmup{std::make_pair(l, r)}, observer(observer) {
DCHECK_NOT_NULL(observer);
}
std::vector<std::pair<const char*, const char*>> warmup;
NodeObserver* observer;
};
class TestSloppyEqualityFactory {
public:
explicit TestSloppyEqualityFactory(Zone* zone) : zone_(zone) {}
NodeObserver* SpeculativeNumberEqual(NumberOperationHint hint) {
return zone_->New<CreationObserver>([hint](const Node* node) {
CHECK_EQ(IrOpcode::kSpeculativeNumberEqual, node->opcode());
CHECK_EQ(hint, NumberOperationHintOf(node->op()));
});
}
NodeObserver* JSEqual(CompareOperationHint /*hint*/) {
return zone_->New<CreationObserver>([](const Node* node) {
CHECK_EQ(IrOpcode::kJSEqual, node->opcode());
// TODO(paolosev): compare hint
});
}
NodeObserver* OperatorChange(IrOpcode::Value created_op,
IrOpcode::Value modified_op) {
return zone_->New<ModificationObserver>(
[created_op](const Node* node) {
CHECK_EQ(created_op, node->opcode());
},
[modified_op](const Node* node, const ObservableNodeState& old_state) {
if (old_state.opcode() != node->opcode()) {
CHECK_EQ(modified_op, node->opcode());
}
});
}
private:
Zone* zone_;
};
TEST(TestSloppyEquality) {
FlagScope<bool> allow_natives_syntax(&i::FLAG_allow_natives_syntax, true);
FlagScope<bool> always_opt(&i::FLAG_always_opt, false);
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone zone(isolate->allocator(), ZONE_NAME);
TestSloppyEqualityFactory f(&zone);
// TODO(nicohartmann@, v8:5660): Collect more precise feedback for some useful
// cases.
TestCase cases[] = {
{"3", "8", f.SpeculativeNumberEqual(NumberOperationHint::kSignedSmall)},
//{"3", "null",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
//{"3", "undefined",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
//{"3", "true",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
{"3", "\"abc\"", f.JSEqual(CompareOperationHint::kAny)},
{"3.14", "3", f.SpeculativeNumberEqual(NumberOperationHint::kNumber)},
//{"3.14", "null",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
//{"3.14", "undefined",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
//{"3.14", "true",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
{"3.14", "\"abc\"", f.JSEqual(CompareOperationHint::kAny)},
{"\"abc\"", "3", f.JSEqual(CompareOperationHint::kAny)},
{"\"abc\"", "null", f.JSEqual(CompareOperationHint::kAny)},
{"\"abc\"", "undefined", f.JSEqual(CompareOperationHint::kAny)},
{"\"abc\"", "true", f.JSEqual(CompareOperationHint::kAny)},
{"\"abc\"", "\"xy\"",
f.JSEqual(CompareOperationHint::kInternalizedString)},
//{"true", "3",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
//{"true", "null",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
//{"true", "undefined",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
//{"true", "true",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
{"true", "\"abc\"", f.JSEqual(CompareOperationHint::kAny)},
//{"undefined", "3",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
{"undefined", "null",
f.JSEqual(CompareOperationHint::kReceiverOrNullOrUndefined)},
{"undefined", "undefined",
f.JSEqual(CompareOperationHint::kReceiverOrNullOrUndefined)},
//{"undefined", "true",
// f.SpeculativeNumberEqual(NumberOperationHint::kNumberOrOddball)},
{"undefined", "\"abc\"", f.JSEqual(CompareOperationHint::kAny)},
{"{}", "3", f.JSEqual(CompareOperationHint::kAny)},
{"{}", "null",
f.JSEqual(CompareOperationHint::kReceiverOrNullOrUndefined)},
{"{}", "undefined",
f.JSEqual(CompareOperationHint::kReceiverOrNullOrUndefined)},
{"{}", "true", f.JSEqual(CompareOperationHint::kAny)},
{"{}", "\"abc\"", f.JSEqual(CompareOperationHint::kAny)},
{"3.14", "3",
f.OperatorChange(IrOpcode::kSpeculativeNumberEqual,
IrOpcode::kFloat64Equal)}};
for (const auto& c : cases) {
std::ostringstream src;
src << "function test(a, b) {\n"
<< " return %ObserveNode(a == b);\n"
<< "}\n"
<< "%PrepareFunctionForOptimization(test);\n";
for (const auto& args : c.warmup) {
src << "test(" << args.first << ", " << args.second << ");\n"
<< "test(" << args.first << ", " << args.second << ");\n";
}
TestWithObserveNode tester(isolate, src.str().c_str());
tester.OptimizeFunctionWithObserver("test", c.observer);
}
}
} // namespace compiler
} // namespace internal
} // namespace v8
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