Commit 4940c0bd authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] Unify frame state inputs.

Now all nodes that care about deoptimization always take frame state
inputs no matter whether deoptimization is enabled for a particular
function. In case that deoptimization is off, the AstGraphBuilder just
inserts the empty frame state. This greatly simplifies the logic in
various places and makes testing easier as well, and is probably the
first step towards enabling --turbo-deoptimization by default.

There seems to be no noticable performance impact on asm.js programs.

Also fix the graph replay in order to regenerate the scheduler unittests.

Review URL: https://codereview.chromium.org/1106613003

Cr-Commit-Position: refs/heads/master@{#28026}
parent 26474269
...@@ -115,6 +115,7 @@ CompilationInfo::CompilationInfo(ParseInfo* parse_info) ...@@ -115,6 +115,7 @@ CompilationInfo::CompilationInfo(ParseInfo* parse_info)
if (isolate_->debug()->is_active()) MarkAsDebug(); if (isolate_->debug()->is_active()) MarkAsDebug();
if (FLAG_context_specialization) MarkAsContextSpecializing(); if (FLAG_context_specialization) MarkAsContextSpecializing();
if (FLAG_turbo_builtin_inlining) MarkAsBuiltinInliningEnabled(); if (FLAG_turbo_builtin_inlining) MarkAsBuiltinInliningEnabled();
if (FLAG_turbo_deoptimization) MarkAsDeoptimizationEnabled();
if (FLAG_turbo_inlining) MarkAsInliningEnabled(); if (FLAG_turbo_inlining) MarkAsInliningEnabled();
if (FLAG_turbo_splitting) MarkAsSplittingEnabled(); if (FLAG_turbo_splitting) MarkAsSplittingEnabled();
if (FLAG_turbo_types) MarkAsTypingEnabled(); if (FLAG_turbo_types) MarkAsTypingEnabled();
......
...@@ -126,7 +126,8 @@ class CompilationInfo { ...@@ -126,7 +126,8 @@ class CompilationInfo {
kDisableFutureOptimization = 1 << 12, kDisableFutureOptimization = 1 << 12,
kSplittingEnabled = 1 << 13, kSplittingEnabled = 1 << 13,
kBuiltinInliningEnabled = 1 << 14, kBuiltinInliningEnabled = 1 << 14,
kTypeFeedbackEnabled = 1 << 15 kTypeFeedbackEnabled = 1 << 15,
kDeoptimizationEnabled = 1 << 16
}; };
explicit CompilationInfo(ParseInfo* parse_info); explicit CompilationInfo(ParseInfo* parse_info);
...@@ -217,6 +218,12 @@ class CompilationInfo { ...@@ -217,6 +218,12 @@ class CompilationInfo {
return GetFlag(kTypeFeedbackEnabled); return GetFlag(kTypeFeedbackEnabled);
} }
void MarkAsDeoptimizationEnabled() { SetFlag(kDeoptimizationEnabled); }
bool is_deoptimization_enabled() const {
return GetFlag(kDeoptimizationEnabled);
}
void MarkAsInliningEnabled() { SetFlag(kInliningEnabled); } void MarkAsInliningEnabled() { SetFlag(kInliningEnabled); }
bool is_inlining_enabled() const { return GetFlag(kInliningEnabled); } bool is_inlining_enabled() const { return GetFlag(kInliningEnabled); }
......
...@@ -738,7 +738,9 @@ void AstGraphBuilder::Environment::UpdateStateValuesWithCache( ...@@ -738,7 +738,9 @@ void AstGraphBuilder::Environment::UpdateStateValuesWithCache(
Node* AstGraphBuilder::Environment::Checkpoint( Node* AstGraphBuilder::Environment::Checkpoint(
BailoutId ast_id, OutputFrameStateCombine combine) { BailoutId ast_id, OutputFrameStateCombine combine) {
if (!FLAG_turbo_deoptimization) return nullptr; if (!builder()->info()->is_deoptimization_enabled()) {
return builder()->jsgraph()->EmptyFrameState();
}
UpdateStateValues(&parameters_node_, 0, parameters_count()); UpdateStateValues(&parameters_node_, 0, parameters_count());
UpdateStateValuesWithCache(&locals_node_, parameters_count(), locals_count()); UpdateStateValuesWithCache(&locals_node_, parameters_count(), locals_count());
......
...@@ -241,6 +241,7 @@ Reduction ChangeLowering::ChangeTaggedToFloat64(Node* value, Node* control) { ...@@ -241,6 +241,7 @@ Reduction ChangeLowering::ChangeTaggedToFloat64(Node* value, Node* control) {
// else LoadHeapNumberValue(y) // else LoadHeapNumberValue(y)
Node* const object = NodeProperties::GetValueInput(value, 0); Node* const object = NodeProperties::GetValueInput(value, 0);
Node* const context = NodeProperties::GetContextInput(value); Node* const context = NodeProperties::GetContextInput(value);
Node* const frame_state = NodeProperties::GetFrameStateInput(value, 0);
Node* const effect = NodeProperties::GetEffectInput(value); Node* const effect = NodeProperties::GetEffectInput(value);
Node* const control = NodeProperties::GetControlInput(value); Node* const control = NodeProperties::GetControlInput(value);
...@@ -253,12 +254,8 @@ Reduction ChangeLowering::ChangeTaggedToFloat64(Node* value, Node* control) { ...@@ -253,12 +254,8 @@ Reduction ChangeLowering::ChangeTaggedToFloat64(Node* value, Node* control) {
graph()->NewNode(common()->Branch(BranchHint::kFalse), check1, control); graph()->NewNode(common()->Branch(BranchHint::kFalse), check1, control);
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1); Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* vtrue1 = Node* vtrue1 = graph()->NewNode(value->op(), object, context, frame_state,
FLAG_turbo_deoptimization effect, if_true1);
? graph()->NewNode(value->op(), object, context,
NodeProperties::GetFrameStateInput(value, 0),
effect, if_true1)
: graph()->NewNode(value->op(), object, context, effect, if_true1);
Node* etrue1 = vtrue1; Node* etrue1 = vtrue1;
{ {
Node* check2 = TestNotSmi(vtrue1); Node* check2 = TestNotSmi(vtrue1);
......
...@@ -19,14 +19,14 @@ namespace compiler { ...@@ -19,14 +19,14 @@ namespace compiler {
void GraphReplayPrinter::PrintReplay(Graph* graph) { void GraphReplayPrinter::PrintReplay(Graph* graph) {
GraphReplayPrinter replay; GraphReplayPrinter replay;
PrintF(" Node* nil = graph.NewNode(common_builder.Dead());\n"); PrintF(" Node* nil = graph()->NewNode(common()->Dead());\n");
Zone zone; Zone zone;
AllNodes nodes(&zone, graph); AllNodes nodes(&zone, graph);
// Allocate the nodes first. // Allocate the nodes first.
for (Node* node : nodes.live) { for (Node* node : nodes.live) {
PrintReplayOpCreator(node->op()); PrintReplayOpCreator(node->op());
PrintF(" Node* n%d = graph.NewNode(op", node->id()); PrintF(" Node* n%d = graph()->NewNode(op", node->id());
for (int i = 0; i < node->InputCount(); ++i) { for (int i = 0; i < node->InputCount(); ++i) {
PrintF(", nil"); PrintF(", nil");
} }
...@@ -45,24 +45,25 @@ void GraphReplayPrinter::PrintReplay(Graph* graph) { ...@@ -45,24 +45,25 @@ void GraphReplayPrinter::PrintReplay(Graph* graph) {
void GraphReplayPrinter::PrintReplayOpCreator(const Operator* op) { void GraphReplayPrinter::PrintReplayOpCreator(const Operator* op) {
IrOpcode::Value opcode = static_cast<IrOpcode::Value>(op->opcode()); IrOpcode::Value opcode = static_cast<IrOpcode::Value>(op->opcode());
const char* builder = const char* builder = IrOpcode::IsCommonOpcode(opcode) ? "common" : "js";
IrOpcode::IsCommonOpcode(opcode) ? "common_builder" : "js_builder";
const char* mnemonic = IrOpcode::IsCommonOpcode(opcode) const char* mnemonic = IrOpcode::IsCommonOpcode(opcode)
? IrOpcode::Mnemonic(opcode) ? IrOpcode::Mnemonic(opcode)
: IrOpcode::Mnemonic(opcode) + 2; : IrOpcode::Mnemonic(opcode) + 2;
PrintF(" op = %s.%s(", builder, mnemonic); PrintF(" op = %s()->%s(", builder, mnemonic);
switch (opcode) { switch (opcode) {
case IrOpcode::kParameter: case IrOpcode::kParameter:
case IrOpcode::kNumberConstant: PrintF("%d", ParameterIndexOf(op));
PrintF("0");
break; break;
case IrOpcode::kLoad: case IrOpcode::kNumberConstant:
PrintF("unique_name"); PrintF("%g", OpParameter<double>(op));
break; break;
case IrOpcode::kHeapConstant: case IrOpcode::kHeapConstant:
PrintF("unique_constant"); PrintF("unique_constant");
break; break;
case IrOpcode::kPhi: case IrOpcode::kPhi:
PrintF("kMachAnyTagged, %d", op->ValueInputCount());
break;
case IrOpcode::kStateValues:
PrintF("%d", op->ValueInputCount()); PrintF("%d", op->ValueInputCount());
break; break;
case IrOpcode::kEffectPhi: case IrOpcode::kEffectPhi:
...@@ -72,6 +73,12 @@ void GraphReplayPrinter::PrintReplayOpCreator(const Operator* op) { ...@@ -72,6 +73,12 @@ void GraphReplayPrinter::PrintReplayOpCreator(const Operator* op) {
case IrOpcode::kMerge: case IrOpcode::kMerge:
PrintF("%d", op->ControlInputCount()); PrintF("%d", op->ControlInputCount());
break; break;
case IrOpcode::kStart:
PrintF("%d", op->ValueOutputCount() - 3);
break;
case IrOpcode::kFrameState:
PrintF("JS_FRAME, BailoutId(-1), OutputFrameStateCombine::Ignore()");
break;
default: default:
break; break;
} }
...@@ -79,6 +86,7 @@ void GraphReplayPrinter::PrintReplayOpCreator(const Operator* op) { ...@@ -79,6 +86,7 @@ void GraphReplayPrinter::PrintReplayOpCreator(const Operator* op) {
} }
#endif // DEBUG #endif // DEBUG
}
} } // namespace compiler
} // namespace v8::internal::compiler } // namespace internal
} // namespace v8
...@@ -69,6 +69,11 @@ class Graph : public ZoneObject { ...@@ -69,6 +69,11 @@ class Graph : public ZoneObject {
Node* nodes[] = {n1, n2, n3, n4, n5, n6, n7}; Node* nodes[] = {n1, n2, n3, n4, n5, n6, n7};
return NewNode(op, arraysize(nodes), nodes); return NewNode(op, arraysize(nodes), nodes);
} }
Node* NewNode(const Operator* op, Node* n1, Node* n2, Node* n3, Node* n4,
Node* n5, Node* n6, Node* n7, Node* n8) {
Node* nodes[] = {n1, n2, n3, n4, n5, n6, n7, n8};
return NewNode(op, arraysize(nodes), nodes);
}
template <class Visitor> template <class Visitor>
inline void VisitNodeInputsFromEnd(Visitor* visitor); inline void VisitNodeInputsFromEnd(Visitor* visitor);
......
...@@ -1057,8 +1057,6 @@ void InstructionSelector::VisitReturn(Node* value) { ...@@ -1057,8 +1057,6 @@ void InstructionSelector::VisitReturn(Node* value) {
void InstructionSelector::VisitDeoptimize(Node* value) { void InstructionSelector::VisitDeoptimize(Node* value) {
DCHECK(FLAG_turbo_deoptimization);
OperandGenerator g(this); OperandGenerator g(this);
FrameStateDescriptor* desc = GetFrameStateDescriptor(value); FrameStateDescriptor* desc = GetFrameStateDescriptor(value);
......
...@@ -138,11 +138,7 @@ void JSGenericLowering::ReplaceWithCompareIC(Node* node, Token::Value token) { ...@@ -138,11 +138,7 @@ void JSGenericLowering::ReplaceWithCompareIC(Node* node, Token::Value token) {
inputs.push_back(graph()->start()); inputs.push_back(graph()->start());
inputs.push_back(graph()->start()); inputs.push_back(graph()->start());
} else { } else {
DCHECK((OperatorProperties::GetFrameStateInputCount(node->op()) == 1) == inputs.push_back(NodeProperties::GetFrameStateInput(node, 0));
FLAG_turbo_deoptimization);
if (FLAG_turbo_deoptimization) {
inputs.push_back(NodeProperties::GetFrameStateInput(node, 0));
}
inputs.push_back(NodeProperties::GetEffectInput(node)); inputs.push_back(NodeProperties::GetEffectInput(node));
inputs.push_back(NodeProperties::GetControlInput(node)); inputs.push_back(NodeProperties::GetControlInput(node));
} }
......
...@@ -360,20 +360,18 @@ Reduction JSInliner::Reduce(Node* node) { ...@@ -360,20 +360,18 @@ Reduction JSInliner::Reduce(Node* node) {
Inlinee inlinee(visitor.GetCopy(graph.start()), visitor.GetCopy(graph.end())); Inlinee inlinee(visitor.GetCopy(graph.start()), visitor.GetCopy(graph.end()));
if (FLAG_turbo_deoptimization) { Node* outer_frame_state = call.frame_state();
Node* outer_frame_state = call.frame_state(); // Insert argument adaptor frame if required.
// Insert argument adaptor frame if required. if (call.formal_arguments() != inlinee.formal_parameters()) {
if (call.formal_arguments() != inlinee.formal_parameters()) { outer_frame_state =
outer_frame_state = CreateArgumentsAdaptorFrameState(&call, function, info.zone());
CreateArgumentsAdaptorFrameState(&call, function, info.zone()); }
}
for (Node* node : visitor.copies()) { for (Node* node : visitor.copies()) {
if (node && node->opcode() == IrOpcode::kFrameState) { if (node && node->opcode() == IrOpcode::kFrameState) {
DCHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(node->op())); DCHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(node->op()));
AddClosureToFrameState(node, function); AddClosureToFrameState(node, function);
NodeProperties::ReplaceFrameStateInput(node, 0, outer_frame_state); NodeProperties::ReplaceFrameStateInput(node, 0, outer_frame_state);
}
} }
} }
......
// Copyright 2015 the V8 project authors. All rights reserved. // Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
...@@ -13,6 +12,7 @@ ...@@ -13,6 +12,7 @@
#include "src/compiler/linkage.h" #include "src/compiler/linkage.h"
#include "src/compiler/node-matchers.h" #include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h" #include "src/compiler/node-properties.h"
#include "src/compiler/operator-properties.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -114,8 +114,9 @@ Reduction JSIntrinsicLowering::ReduceCreateArrayLiteral(Node* node) { ...@@ -114,8 +114,9 @@ Reduction JSIntrinsicLowering::ReduceCreateArrayLiteral(Node* node) {
Callable callable = CodeFactory::FastCloneShallowArray(isolate); Callable callable = CodeFactory::FastCloneShallowArray(isolate);
CallDescriptor* desc = Linkage::GetStubCallDescriptor( CallDescriptor* desc = Linkage::GetStubCallDescriptor(
isolate, graph()->zone(), callable.descriptor(), 0, isolate, graph()->zone(), callable.descriptor(), 0,
FLAG_turbo_deoptimization ? CallDescriptor::kNeedsFrameState (OperatorProperties::GetFrameStateInputCount(node->op()) != 0)
: CallDescriptor::kNoFlags); ? CallDescriptor::kNeedsFrameState
: CallDescriptor::kNoFlags);
const Operator* new_op = common()->Call(desc); const Operator* new_op = common()->Call(desc);
Node* stub_code = jsgraph()->HeapConstant(callable.code()); Node* stub_code = jsgraph()->HeapConstant(callable.code());
node->RemoveInput(3); // Remove flags input from node. node->RemoveInput(3); // Remove flags input from node.
...@@ -143,8 +144,9 @@ Reduction JSIntrinsicLowering::ReduceCreateObjectLiteral(Node* node) { ...@@ -143,8 +144,9 @@ Reduction JSIntrinsicLowering::ReduceCreateObjectLiteral(Node* node) {
Callable callable = CodeFactory::FastCloneShallowObject(isolate, length); Callable callable = CodeFactory::FastCloneShallowObject(isolate, length);
CallDescriptor* desc = Linkage::GetStubCallDescriptor( CallDescriptor* desc = Linkage::GetStubCallDescriptor(
isolate, graph()->zone(), callable.descriptor(), 0, isolate, graph()->zone(), callable.descriptor(), 0,
FLAG_turbo_deoptimization ? CallDescriptor::kNeedsFrameState (OperatorProperties::GetFrameStateInputCount(node->op()) != 0)
: CallDescriptor::kNoFlags); ? CallDescriptor::kNeedsFrameState
: CallDescriptor::kNoFlags);
const Operator* new_op = common()->Call(desc); const Operator* new_op = common()->Call(desc);
Node* stub_code = jsgraph()->HeapConstant(callable.code()); Node* stub_code = jsgraph()->HeapConstant(callable.code());
node->InsertInput(graph()->zone(), 0, stub_code); node->InsertInput(graph()->zone(), 0, stub_code);
...@@ -157,6 +159,7 @@ Reduction JSIntrinsicLowering::ReduceCreateObjectLiteral(Node* node) { ...@@ -157,6 +159,7 @@ Reduction JSIntrinsicLowering::ReduceCreateObjectLiteral(Node* node) {
Reduction JSIntrinsicLowering::ReduceDeoptimizeNow(Node* node) { Reduction JSIntrinsicLowering::ReduceDeoptimizeNow(Node* node) {
// TODO(jarin): This should not depend on the global flag.
if (!FLAG_turbo_deoptimization) return NoChange(); if (!FLAG_turbo_deoptimization) return NoChange();
Node* frame_state = NodeProperties::GetFrameStateInput(node, 0); Node* frame_state = NodeProperties::GetFrameStateInput(node, 0);
......
...@@ -61,11 +61,9 @@ Reduction JSTypeFeedbackSpecializer::Reduce(Node* node) { ...@@ -61,11 +61,9 @@ Reduction JSTypeFeedbackSpecializer::Reduce(Node* node) {
// StoreProperty(o, "constant", v) => StoreNamed["constant"](o, v). // StoreProperty(o, "constant", v) => StoreNamed["constant"](o, v).
Unique<Name> name = match.Value(); Unique<Name> name = match.Value();
LanguageMode language_mode = OpParameter<LanguageMode>(node); LanguageMode language_mode = OpParameter<LanguageMode>(node);
if (FLAG_turbo_deoptimization) { // StoreProperty has 2 frame state inputs, but StoreNamed only 1.
// StoreProperty has 2 frame state inputs, but StoreNamed only 1. DCHECK_EQ(2, OperatorProperties::GetFrameStateInputCount(node->op()));
DCHECK_EQ(2, OperatorProperties::GetFrameStateInputCount(node->op())); node->RemoveInput(NodeProperties::FirstFrameStateIndex(node) + 1);
node->RemoveInput(NodeProperties::FirstFrameStateIndex(node) + 1);
}
node->set_op( node->set_op(
jsgraph()->javascript()->StoreNamed(language_mode, name, KEYED)); jsgraph()->javascript()->StoreNamed(language_mode, name, KEYED));
node->RemoveInput(1); node->RemoveInput(1);
...@@ -148,8 +146,6 @@ static bool GetInObjectFieldAccess(LoadOrStore mode, Handle<Map> map, ...@@ -148,8 +146,6 @@ static bool GetInObjectFieldAccess(LoadOrStore mode, Handle<Map> map,
Reduction JSTypeFeedbackSpecializer::ReduceJSLoadNamed(Node* node) { Reduction JSTypeFeedbackSpecializer::ReduceJSLoadNamed(Node* node) {
DCHECK(node->opcode() == IrOpcode::kJSLoadNamed); DCHECK(node->opcode() == IrOpcode::kJSLoadNamed);
// TODO(turbofan): type feedback currently requires deoptimization.
if (!FLAG_turbo_deoptimization) return NoChange();
// TODO(titzer): deopt locations are wrong for property accesses // TODO(titzer): deopt locations are wrong for property accesses
if (!EAGER_DEOPT_LOCATIONS_FOR_PROPERTY_ACCESS_ARE_CORRECT) return NoChange(); if (!EAGER_DEOPT_LOCATIONS_FOR_PROPERTY_ACCESS_ARE_CORRECT) return NoChange();
...@@ -202,8 +198,6 @@ Reduction JSTypeFeedbackSpecializer::ReduceJSLoadProperty(Node* node) { ...@@ -202,8 +198,6 @@ Reduction JSTypeFeedbackSpecializer::ReduceJSLoadProperty(Node* node) {
Reduction JSTypeFeedbackSpecializer::ReduceJSStoreNamed(Node* node) { Reduction JSTypeFeedbackSpecializer::ReduceJSStoreNamed(Node* node) {
DCHECK(node->opcode() == IrOpcode::kJSStoreNamed); DCHECK(node->opcode() == IrOpcode::kJSStoreNamed);
// TODO(turbofan): type feedback currently requires deoptimization.
if (!FLAG_turbo_deoptimization) return NoChange();
// TODO(titzer): deopt locations are wrong for property accesses // TODO(titzer): deopt locations are wrong for property accesses
if (!EAGER_DEOPT_LOCATIONS_FOR_PROPERTY_ACCESS_ARE_CORRECT) return NoChange(); if (!EAGER_DEOPT_LOCATIONS_FOR_PROPERTY_ACCESS_ARE_CORRECT) return NoChange();
......
...@@ -211,10 +211,14 @@ class JSBinopReduction final { ...@@ -211,10 +211,14 @@ class JSBinopReduction final {
} }
Node* CreateFrameStateForLeftInput(Node* frame_state) { Node* CreateFrameStateForLeftInput(Node* frame_state) {
if (!FLAG_turbo_deoptimization) return nullptr;
FrameStateCallInfo state_info = FrameStateCallInfo state_info =
OpParameter<FrameStateCallInfo>(frame_state); OpParameter<FrameStateCallInfo>(frame_state);
if (state_info.bailout_id() == BailoutId::None()) {
// Dummy frame state => just leave it as is.
return frame_state;
}
// If the frame state is already the right one, just return it. // If the frame state is already the right one, just return it.
if (state_info.state_combine().kind() == OutputFrameStateCombine::kPokeAt && if (state_info.state_combine().kind() == OutputFrameStateCombine::kPokeAt &&
state_info.state_combine().GetOffsetToPokeAt() == 1) { state_info.state_combine().GetOffsetToPokeAt() == 1) {
...@@ -234,8 +238,6 @@ class JSBinopReduction final { ...@@ -234,8 +238,6 @@ class JSBinopReduction final {
} }
Node* CreateFrameStateForRightInput(Node* frame_state, Node* converted_left) { Node* CreateFrameStateForRightInput(Node* frame_state, Node* converted_left) {
if (!FLAG_turbo_deoptimization) return nullptr;
FrameStateCallInfo state_info = FrameStateCallInfo state_info =
OpParameter<FrameStateCallInfo>(frame_state); OpParameter<FrameStateCallInfo>(frame_state);
...@@ -279,13 +281,6 @@ class JSBinopReduction final { ...@@ -279,13 +281,6 @@ class JSBinopReduction final {
Node* ConvertToNumber(Node* node, Node* frame_state) { Node* ConvertToNumber(Node* node, Node* frame_state) {
if (NodeProperties::GetBounds(node).upper->Is(Type::PlainPrimitive())) { if (NodeProperties::GetBounds(node).upper->Is(Type::PlainPrimitive())) {
return ConvertPrimitiveToNumber(node); return ConvertPrimitiveToNumber(node);
} else if (!FLAG_turbo_deoptimization) {
// We cannot use ConvertToPrimitiveNumber here because we need context
// for converting general values.
Node* const n = graph()->NewNode(javascript()->ToNumber(), node,
context(), effect(), control());
update_effect(n);
return n;
} else { } else {
Node* const n = Node* const n =
graph()->NewNode(javascript()->ToNumber(), node, context(), graph()->NewNode(javascript()->ToNumber(), node, context(),
...@@ -325,9 +320,7 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) { ...@@ -325,9 +320,7 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
} }
if (r.NeitherInputCanBe(Type::StringOrReceiver())) { if (r.NeitherInputCanBe(Type::StringOrReceiver())) {
// JSAdd(x:-string, y:-string) => NumberAdd(ToNumber(x), ToNumber(y)) // JSAdd(x:-string, y:-string) => NumberAdd(ToNumber(x), ToNumber(y))
Node* frame_state = FLAG_turbo_deoptimization Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
? NodeProperties::GetFrameStateInput(node, 1)
: nullptr;
r.ConvertInputsToNumber(frame_state); r.ConvertInputsToNumber(frame_state);
return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number()); return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number());
} }
...@@ -351,9 +344,7 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) { ...@@ -351,9 +344,7 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
Reduction JSTypedLowering::ReduceNumberBinop(Node* node, Reduction JSTypedLowering::ReduceNumberBinop(Node* node,
const Operator* numberOp) { const Operator* numberOp) {
JSBinopReduction r(this, node); JSBinopReduction r(this, node);
Node* frame_state = FLAG_turbo_deoptimization Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
? NodeProperties::GetFrameStateInput(node, 1)
: nullptr;
r.ConvertInputsToNumber(frame_state); r.ConvertInputsToNumber(frame_state);
return r.ChangeToPureOperator(numberOp, Type::Number()); return r.ChangeToPureOperator(numberOp, Type::Number());
} }
...@@ -361,9 +352,7 @@ Reduction JSTypedLowering::ReduceNumberBinop(Node* node, ...@@ -361,9 +352,7 @@ Reduction JSTypedLowering::ReduceNumberBinop(Node* node,
Reduction JSTypedLowering::ReduceInt32Binop(Node* node, const Operator* intOp) { Reduction JSTypedLowering::ReduceInt32Binop(Node* node, const Operator* intOp) {
JSBinopReduction r(this, node); JSBinopReduction r(this, node);
Node* frame_state = FLAG_turbo_deoptimization Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
? NodeProperties::GetFrameStateInput(node, 1)
: nullptr;
r.ConvertInputsToNumber(frame_state); r.ConvertInputsToNumber(frame_state);
r.ConvertInputsToUI32(kSigned, kSigned); r.ConvertInputsToUI32(kSigned, kSigned);
return r.ChangeToPureOperator(intOp, Type::Integral32()); return r.ChangeToPureOperator(intOp, Type::Integral32());
...@@ -700,11 +689,9 @@ Reduction JSTypedLowering::ReduceJSToNumber(Node* node) { ...@@ -700,11 +689,9 @@ Reduction JSTypedLowering::ReduceJSToNumber(Node* node) {
NodeProperties::ReplaceContextInput(node, jsgraph()->NoContextConstant()); NodeProperties::ReplaceContextInput(node, jsgraph()->NoContextConstant());
NodeProperties::ReplaceControlInput(node, graph()->start()); NodeProperties::ReplaceControlInput(node, graph()->start());
NodeProperties::ReplaceEffectInput(node, graph()->start()); NodeProperties::ReplaceEffectInput(node, graph()->start());
if (FLAG_turbo_deoptimization) { DCHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(node->op()));
DCHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(node->op())); NodeProperties::ReplaceFrameStateInput(node, 0,
NodeProperties::ReplaceFrameStateInput(node, 0, jsgraph()->EmptyFrameState());
jsgraph()->EmptyFrameState());
}
return Changed(node); return Changed(node);
} }
} }
...@@ -841,19 +828,11 @@ Reduction JSTypedLowering::ReduceJSStoreProperty(Node* node) { ...@@ -841,19 +828,11 @@ Reduction JSTypedLowering::ReduceJSStoreProperty(Node* node) {
if (number_reduction.Changed()) { if (number_reduction.Changed()) {
value = number_reduction.replacement(); value = number_reduction.replacement();
} else { } else {
DCHECK(FLAG_turbo_deoptimization == Node* frame_state_for_to_number =
(OperatorProperties::GetFrameStateInputCount( NodeProperties::GetFrameStateInput(node, 1);
javascript()->ToNumber()) == 1)); value = effect =
if (FLAG_turbo_deoptimization) { graph()->NewNode(javascript()->ToNumber(), value, context,
Node* frame_state_for_to_number = frame_state_for_to_number, effect, control);
NodeProperties::GetFrameStateInput(node, 1);
value = effect =
graph()->NewNode(javascript()->ToNumber(), value, context,
frame_state_for_to_number, effect, control);
} else {
value = effect = graph()->NewNode(javascript()->ToNumber(), value,
context, effect, control);
}
} }
} }
// For integer-typed arrays, convert to the integer type. // For integer-typed arrays, convert to the integer type.
...@@ -1036,15 +1015,9 @@ Node* JSTypedLowering::ConvertPrimitiveToNumber(Node* input) { ...@@ -1036,15 +1015,9 @@ Node* JSTypedLowering::ConvertPrimitiveToNumber(Node* input) {
Reduction const reduction = ReduceJSToNumberInput(input); Reduction const reduction = ReduceJSToNumberInput(input);
if (reduction.Changed()) return reduction.replacement(); if (reduction.Changed()) return reduction.replacement();
// TODO(jarin) Use PlainPrimitiveToNumber once we have it. // TODO(jarin) Use PlainPrimitiveToNumber once we have it.
Node* const conversion = Node* const conversion = graph()->NewNode(
FLAG_turbo_deoptimization javascript()->ToNumber(), input, jsgraph()->NoContextConstant(),
? graph()->NewNode(javascript()->ToNumber(), input, jsgraph()->EmptyFrameState(), graph()->start(), graph()->start());
jsgraph()->NoContextConstant(),
jsgraph()->EmptyFrameState(), graph()->start(),
graph()->start())
: graph()->NewNode(javascript()->ToNumber(), input,
jsgraph()->NoContextConstant(), graph()->start(),
graph()->start());
InsertConversion(conversion); InsertConversion(conversion);
return conversion; return conversion;
} }
......
...@@ -96,10 +96,6 @@ FrameOffset Linkage::GetFrameOffset(int spill_slot, Frame* frame, ...@@ -96,10 +96,6 @@ FrameOffset Linkage::GetFrameOffset(int spill_slot, Frame* frame,
// static // static
bool Linkage::NeedsFrameState(Runtime::FunctionId function) { bool Linkage::NeedsFrameState(Runtime::FunctionId function) {
if (!FLAG_turbo_deoptimization) {
return false;
}
// Most runtime functions need a FrameState. A few chosen ones that we know // Most runtime functions need a FrameState. A few chosen ones that we know
// not to call into arbitrary JavaScript, not to throw, and not to deoptimize // not to call into arbitrary JavaScript, not to throw, and not to deoptimize
// are blacklisted here and can be called without a FrameState. // are blacklisted here and can be called without a FrameState.
......
...@@ -21,9 +21,6 @@ bool OperatorProperties::HasContextInput(const Operator* op) { ...@@ -21,9 +21,6 @@ bool OperatorProperties::HasContextInput(const Operator* op) {
// static // static
int OperatorProperties::GetFrameStateInputCount(const Operator* op) { int OperatorProperties::GetFrameStateInputCount(const Operator* op) {
if (!FLAG_turbo_deoptimization) {
return 0;
}
switch (op->opcode()) { switch (op->opcode()) {
case IrOpcode::kFrameState: case IrOpcode::kFrameState:
return 1; return 1;
......
...@@ -1042,7 +1042,9 @@ Handle<Code> Pipeline::GenerateCode() { ...@@ -1042,7 +1042,9 @@ Handle<Code> Pipeline::GenerateCode() {
RunPrintAndVerify("OSR deconstruction"); RunPrintAndVerify("OSR deconstruction");
} }
if (info()->is_type_feedback_enabled()) { // TODO(turbofan): Type feedback currently requires deoptimization.
if (info()->is_deoptimization_enabled() &&
info()->is_type_feedback_enabled()) {
Run<JSTypeFeedbackPhase>(); Run<JSTypeFeedbackPhase>();
RunPrintAndVerify("JSType feedback"); RunPrintAndVerify("JSType feedback");
} }
......
...@@ -76,11 +76,8 @@ TEST_F(ControlFlowOptimizerTest, BuildSwitch1) { ...@@ -76,11 +76,8 @@ TEST_F(ControlFlowOptimizerTest, BuildSwitch1) {
TEST_F(ControlFlowOptimizerTest, BuildSwitch2) { TEST_F(ControlFlowOptimizerTest, BuildSwitch2) {
Node* input = Parameter(0); Node* input = Parameter(0);
Node* context = Parameter(1); Node* context = Parameter(1);
Node* index = FLAG_turbo_deoptimization Node* index = graph()->NewNode(javascript()->ToNumber(), input, context,
? graph()->NewNode(javascript()->ToNumber(), input, context, EmptyFrameState(), start(), start());
EmptyFrameState(), start(), start())
: graph()->NewNode(javascript()->ToNumber(), input, context,
start(), start());
Node* if_success = graph()->NewNode(common()->IfSuccess(), index); Node* if_success = graph()->NewNode(common()->IfSuccess(), index);
Node* branch0 = graph()->NewNode( Node* branch0 = graph()->NewNode(
common()->Branch(), common()->Branch(),
......
...@@ -38,6 +38,12 @@ class JSIntrinsicLoweringTest : public GraphTest { ...@@ -38,6 +38,12 @@ class JSIntrinsicLoweringTest : public GraphTest {
return reducer.Reduce(node); return reducer.Reduce(node);
} }
Node* EmptyFrameState() {
MachineOperatorBuilder machine(zone());
JSGraph jsgraph(isolate(), graph(), common(), javascript(), &machine);
return jsgraph.EmptyFrameState();
}
JSOperatorBuilder* javascript() { return &javascript_; } JSOperatorBuilder* javascript() { return &javascript_; }
private: private:
...@@ -71,23 +77,23 @@ TEST_F(JSIntrinsicLoweringTest, InlineOptimizedConstructDouble) { ...@@ -71,23 +77,23 @@ TEST_F(JSIntrinsicLoweringTest, InlineOptimizedConstructDouble) {
TEST_F(JSIntrinsicLoweringTest, InlineCreateArrayLiteral) { TEST_F(JSIntrinsicLoweringTest, InlineCreateArrayLiteral) {
i::FLAG_turbo_deoptimization = false;
Node* const input0 = Parameter(0); Node* const input0 = Parameter(0);
Node* const input1 = Parameter(1); Node* const input1 = Parameter(1);
Node* const input2 = HeapConstant(factory()->NewFixedArray(12)); Node* const input2 = HeapConstant(factory()->NewFixedArray(12));
Node* const input3 = NumberConstant(ArrayLiteral::kShallowElements); Node* const input3 = NumberConstant(ArrayLiteral::kShallowElements);
Node* const context = Parameter(2); Node* const context = Parameter(2);
Node* const frame_state = EmptyFrameState();
Node* const effect = graph()->start(); Node* const effect = graph()->start();
Node* const control = graph()->start(); Node* const control = graph()->start();
Reduction const r = Reduce(graph()->NewNode( Reduction const r = Reduce(graph()->NewNode(
javascript()->CallRuntime(Runtime::kInlineCreateArrayLiteral, 4), input0, javascript()->CallRuntime(Runtime::kInlineCreateArrayLiteral, 4), input0,
input1, input2, input3, context, effect, control)); input1, input2, input3, context, frame_state, effect, control));
ASSERT_TRUE(r.Changed()); ASSERT_TRUE(r.Changed());
EXPECT_THAT( EXPECT_THAT(
r.replacement(), r.replacement(),
IsCall(_, IsHeapConstant(Unique<HeapObject>::CreateImmovable( IsCall(_, IsHeapConstant(Unique<HeapObject>::CreateImmovable(
CodeFactory::FastCloneShallowArray(isolate()).code())), CodeFactory::FastCloneShallowArray(isolate()).code())),
input0, input1, input2, effect, control)); input0, input1, input2, context, frame_state, effect, control));
} }
...@@ -96,23 +102,24 @@ TEST_F(JSIntrinsicLoweringTest, InlineCreateArrayLiteral) { ...@@ -96,23 +102,24 @@ TEST_F(JSIntrinsicLoweringTest, InlineCreateArrayLiteral) {
TEST_F(JSIntrinsicLoweringTest, InlineCreateObjectLiteral) { TEST_F(JSIntrinsicLoweringTest, InlineCreateObjectLiteral) {
i::FLAG_turbo_deoptimization = false;
Node* const input0 = Parameter(0); Node* const input0 = Parameter(0);
Node* const input1 = Parameter(1); Node* const input1 = Parameter(1);
Node* const input2 = HeapConstant(factory()->NewFixedArray(2 * 6)); Node* const input2 = HeapConstant(factory()->NewFixedArray(2 * 6));
Node* const input3 = NumberConstant(ObjectLiteral::kShallowProperties); Node* const input3 = NumberConstant(ObjectLiteral::kShallowProperties);
Node* const context = Parameter(2); Node* const context = Parameter(2);
Node* const frame_state = EmptyFrameState();
Node* const effect = graph()->start(); Node* const effect = graph()->start();
Node* const control = graph()->start(); Node* const control = graph()->start();
Reduction const r = Reduce(graph()->NewNode( Reduction const r = Reduce(graph()->NewNode(
javascript()->CallRuntime(Runtime::kInlineCreateObjectLiteral, 4), input0, javascript()->CallRuntime(Runtime::kInlineCreateObjectLiteral, 4), input0,
input1, input2, input3, context, effect, control)); input1, input2, input3, context, frame_state, effect, control));
ASSERT_TRUE(r.Changed()); ASSERT_TRUE(r.Changed());
EXPECT_THAT( EXPECT_THAT(
r.replacement(), r.replacement(),
IsCall(_, IsHeapConstant(Unique<HeapObject>::CreateImmovable( IsCall(_, IsHeapConstant(Unique<HeapObject>::CreateImmovable(
CodeFactory::FastCloneShallowObject(isolate(), 6).code())), CodeFactory::FastCloneShallowObject(isolate(), 6).code())),
input0, input1, input2, effect, control)); input0, input1, input2, input3, context, frame_state, effect,
control));
} }
......
...@@ -107,17 +107,14 @@ TEST_P(JSSharedOperatorTest, NumberOfInputsAndOutputs) { ...@@ -107,17 +107,14 @@ TEST_P(JSSharedOperatorTest, NumberOfInputsAndOutputs) {
const Operator* op = (javascript.*sop.constructor)(); const Operator* op = (javascript.*sop.constructor)();
const int context_input_count = 1; const int context_input_count = 1;
// TODO(jarin): Get rid of this hack.
const int frame_state_input_count =
FLAG_turbo_deoptimization ? sop.frame_state_input_count : 0;
EXPECT_EQ(sop.value_input_count, op->ValueInputCount()); EXPECT_EQ(sop.value_input_count, op->ValueInputCount());
EXPECT_EQ(context_input_count, OperatorProperties::GetContextInputCount(op)); EXPECT_EQ(context_input_count, OperatorProperties::GetContextInputCount(op));
EXPECT_EQ(frame_state_input_count, EXPECT_EQ(sop.frame_state_input_count,
OperatorProperties::GetFrameStateInputCount(op)); OperatorProperties::GetFrameStateInputCount(op));
EXPECT_EQ(sop.effect_input_count, op->EffectInputCount()); EXPECT_EQ(sop.effect_input_count, op->EffectInputCount());
EXPECT_EQ(sop.control_input_count, op->ControlInputCount()); EXPECT_EQ(sop.control_input_count, op->ControlInputCount());
EXPECT_EQ(sop.value_input_count + context_input_count + EXPECT_EQ(sop.value_input_count + context_input_count +
frame_state_input_count + sop.effect_input_count + sop.frame_state_input_count + sop.effect_input_count +
sop.control_input_count, sop.control_input_count,
OperatorProperties::GetTotalInputCount(op)); OperatorProperties::GetTotalInputCount(op));
...@@ -169,16 +166,12 @@ TEST_P(JSStorePropertyOperatorTest, NumberOfInputsAndOutputs) { ...@@ -169,16 +166,12 @@ TEST_P(JSStorePropertyOperatorTest, NumberOfInputsAndOutputs) {
const LanguageMode mode = GetParam(); const LanguageMode mode = GetParam();
const Operator* op = javascript.StoreProperty(mode); const Operator* op = javascript.StoreProperty(mode);
// TODO(jarin): Get rid of this hack.
const int frame_state_input_count = FLAG_turbo_deoptimization ? 2 : 0;
EXPECT_EQ(3, op->ValueInputCount()); EXPECT_EQ(3, op->ValueInputCount());
EXPECT_EQ(1, OperatorProperties::GetContextInputCount(op)); EXPECT_EQ(1, OperatorProperties::GetContextInputCount(op));
EXPECT_EQ(frame_state_input_count, EXPECT_EQ(2, OperatorProperties::GetFrameStateInputCount(op));
OperatorProperties::GetFrameStateInputCount(op));
EXPECT_EQ(1, op->EffectInputCount()); EXPECT_EQ(1, op->EffectInputCount());
EXPECT_EQ(1, op->ControlInputCount()); EXPECT_EQ(1, op->ControlInputCount());
EXPECT_EQ(6 + frame_state_input_count, EXPECT_EQ(8, OperatorProperties::GetTotalInputCount(op));
OperatorProperties::GetTotalInputCount(op));
EXPECT_EQ(0, op->ValueOutputCount()); EXPECT_EQ(0, op->ValueOutputCount());
EXPECT_EQ(1, op->EffectOutputCount()); EXPECT_EQ(1, op->EffectOutputCount());
......
...@@ -417,11 +417,8 @@ TEST_F(JSTypedLoweringTest, JSToNumberWithPlainPrimitive) { ...@@ -417,11 +417,8 @@ TEST_F(JSTypedLoweringTest, JSToNumberWithPlainPrimitive) {
Node* const effect = graph()->start(); Node* const effect = graph()->start();
Node* const control = graph()->start(); Node* const control = graph()->start();
Reduction r = Reduction r =
FLAG_turbo_deoptimization Reduce(graph()->NewNode(javascript()->ToNumber(), input, context,
? Reduce(graph()->NewNode(javascript()->ToNumber(), input, context, EmptyFrameState(), effect, control));
EmptyFrameState(), effect, control))
: Reduce(graph()->NewNode(javascript()->ToNumber(), input, context,
effect, control));
ASSERT_TRUE(r.Changed()); ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsToNumber(input, IsNumberConstant(BitEq(0.0)), EXPECT_THAT(r.replacement(), IsToNumber(input, IsNumberConstant(BitEq(0.0)),
graph()->start(), control)); graph()->start(), control));
...@@ -639,14 +636,9 @@ TEST_F(JSTypedLoweringTest, JSLoadPropertyFromExternalTypedArray) { ...@@ -639,14 +636,9 @@ TEST_F(JSTypedLoweringTest, JSLoadPropertyFromExternalTypedArray) {
Node* context = UndefinedConstant(); Node* context = UndefinedConstant();
Node* effect = graph()->start(); Node* effect = graph()->start();
Node* control = graph()->start(); Node* control = graph()->start();
Node* node = graph()->NewNode(javascript()->LoadProperty(feedback), base, Reduction r =
key, context); Reduce(graph()->NewNode(javascript()->LoadProperty(feedback), base, key,
if (FLAG_turbo_deoptimization) { context, EmptyFrameState(), effect, control));
node->AppendInput(zone(), UndefinedConstant());
}
node->AppendInput(zone(), effect);
node->AppendInput(zone(), control);
Reduction r = Reduce(node);
Matcher<Node*> offset_matcher = Matcher<Node*> offset_matcher =
element_size == 1 element_size == 1
...@@ -685,14 +677,9 @@ TEST_F(JSTypedLoweringTest, JSLoadPropertyFromExternalTypedArrayWithSafeKey) { ...@@ -685,14 +677,9 @@ TEST_F(JSTypedLoweringTest, JSLoadPropertyFromExternalTypedArrayWithSafeKey) {
Node* context = UndefinedConstant(); Node* context = UndefinedConstant();
Node* effect = graph()->start(); Node* effect = graph()->start();
Node* control = graph()->start(); Node* control = graph()->start();
Node* node = graph()->NewNode(javascript()->LoadProperty(feedback), base, Reduction r =
key, context); Reduce(graph()->NewNode(javascript()->LoadProperty(feedback), base, key,
if (FLAG_turbo_deoptimization) { context, EmptyFrameState(), effect, control));
node->AppendInput(zone(), UndefinedConstant());
}
node->AppendInput(zone(), effect);
node->AppendInput(zone(), control);
Reduction r = Reduce(node);
ASSERT_TRUE(r.Changed()); ASSERT_TRUE(r.Changed());
EXPECT_THAT( EXPECT_THAT(
...@@ -875,16 +862,11 @@ TEST_F(JSTypedLoweringTest, JSLoadNamedGlobalConstants) { ...@@ -875,16 +862,11 @@ TEST_F(JSTypedLoweringTest, JSLoadNamedGlobalConstants) {
for (size_t i = 0; i < arraysize(names); i++) { for (size_t i = 0; i < arraysize(names); i++) {
Unique<Name> name = Unique<Name>::CreateImmovable(names[i]); Unique<Name> name = Unique<Name>::CreateImmovable(names[i]);
Node* node = graph()->NewNode(javascript()->LoadNamed(name, feedback), Reduction r =
global, context); Reduce(graph()->NewNode(javascript()->LoadNamed(name, feedback), global,
if (FLAG_turbo_deoptimization) { context, EmptyFrameState(), effect, control));
node->AppendInput(zone(), EmptyFrameState());
}
node->AppendInput(zone(), effect);
node->AppendInput(zone(), control);
Reduction r = Reduce(node);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), matches[i]); EXPECT_THAT(r.replacement(), matches[i]);
} }
} }
......
This diff is collapsed.
...@@ -98,8 +98,25 @@ Matcher<Node*> IsCall(const Matcher<CallDescriptor*>& descriptor_matcher, ...@@ -98,8 +98,25 @@ Matcher<Node*> IsCall(const Matcher<CallDescriptor*>& descriptor_matcher,
const Matcher<Node*>& value1_matcher, const Matcher<Node*>& value1_matcher,
const Matcher<Node*>& value2_matcher, const Matcher<Node*>& value2_matcher,
const Matcher<Node*>& value3_matcher, const Matcher<Node*>& value3_matcher,
const Matcher<Node*>& value4_matcher,
const Matcher<Node*>& effect_matcher, const Matcher<Node*>& effect_matcher,
const Matcher<Node*>& control_matcher); const Matcher<Node*>& control_matcher);
Matcher<Node*> IsCall(const Matcher<CallDescriptor*>& descriptor_matcher,
const Matcher<Node*>& value0_matcher,
const Matcher<Node*>& value1_matcher,
const Matcher<Node*>& value2_matcher,
const Matcher<Node*>& value3_matcher,
const Matcher<Node*>& value4_matcher,
const Matcher<Node*>& value5_matcher,
const Matcher<Node*>& effect_matcher,
const Matcher<Node*>& control_matcher);
Matcher<Node*> IsCall(
const Matcher<CallDescriptor*>& descriptor_matcher,
const Matcher<Node*>& value0_matcher, const Matcher<Node*>& value1_matcher,
const Matcher<Node*>& value2_matcher, const Matcher<Node*>& value3_matcher,
const Matcher<Node*>& value4_matcher, const Matcher<Node*>& value5_matcher,
const Matcher<Node*>& value6_matcher, const Matcher<Node*>& effect_matcher,
const Matcher<Node*>& control_matcher);
Matcher<Node*> IsBooleanNot(const Matcher<Node*>& value_matcher); Matcher<Node*> IsBooleanNot(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberEqual(const Matcher<Node*>& lhs_matcher, Matcher<Node*> IsNumberEqual(const Matcher<Node*>& lhs_matcher,
......
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