Commit ed0039a7 authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] Unify the PlainPrimitive as Number treatment.

Now that we have the PlainPrimitiveToNumber operator(s), we can unify
all the places where we expect a number, but can also safely handle any
plain-primitive (via ToNumber truncation).

Drive-by-fix: Also handle Math.min consistently with Math.max.

R=jarin@chromium.org

Review-Url: https://codereview.chromium.org/2064953004
Cr-Commit-Position: refs/heads/master@{#36984}
parent 05a663e6
This diff is collapsed.
......@@ -18,7 +18,6 @@ namespace compiler {
// Forward declarations.
class CommonOperatorBuilder;
class JSGraph;
class MachineOperatorBuilder;
class SimplifiedOperatorBuilder;
......@@ -30,27 +29,29 @@ class JSBuiltinReducer final : public AdvancedReducer {
Reduction Reduce(Node* node) final;
private:
Reduction ReduceFunctionCall(Node* node);
Reduction ReduceMathMax(Node* node);
Reduction ReduceMathImul(Node* node);
Reduction ReduceMathAtan(Node* node);
Reduction ReduceMathAtan2(Node* node);
Reduction ReduceMathCeil(Node* node);
Reduction ReduceMathClz32(Node* node);
Reduction ReduceMathFloor(Node* node);
Reduction ReduceMathFround(Node* node);
Reduction ReduceMathAtan(Node* node);
Reduction ReduceMathAtan2(Node* node);
Reduction ReduceMathImul(Node* node);
Reduction ReduceMathLog(Node* node);
Reduction ReduceMathLog1p(Node* node);
Reduction ReduceMathMax(Node* node);
Reduction ReduceMathMin(Node* node);
Reduction ReduceMathRound(Node* node);
Reduction ReduceMathSqrt(Node* node);
Reduction ReduceMathTrunc(Node* node);
Reduction ReduceStringFromCharCode(Node* node);
Node* ToNumber(Node* value);
Node* ToUint32(Node* value);
Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; }
Isolate* isolate() const;
CommonOperatorBuilder* common() const;
MachineOperatorBuilder* machine() const;
SimplifiedOperatorBuilder* simplified() const;
JSGraph* const jsgraph_;
......
......@@ -45,7 +45,7 @@ class JSBinopReduction final {
return BinaryOperationHints::kAny;
}
void ConvertInputsToNumberOrUndefined(Node* frame_state) {
void ConvertInputsToNumber(Node* frame_state) {
// To convert the inputs to numbers, we have to provide frame states
// for lazy bailouts in the ToNumber conversions.
// We use a little hack here: we take the frame state before the binary
......@@ -64,11 +64,11 @@ class JSBinopReduction final {
ConvertBothInputsToNumber(&left_input, &right_input, frame_state);
} else {
left_input = left_is_primitive
? ConvertPlainPrimitiveToNumberOrUndefined(left())
? ConvertPlainPrimitiveToNumber(left())
: ConvertSingleInputToNumber(
left(), CreateFrameStateForLeftInput(frame_state));
right_input = right_is_primitive
? ConvertPlainPrimitiveToNumberOrUndefined(right())
? ConvertPlainPrimitiveToNumber(right())
: ConvertSingleInputToNumber(
right(), CreateFrameStateForRightInput(
frame_state, left_input));
......@@ -281,12 +281,12 @@ class JSBinopReduction final {
frame_state->InputAt(kFrameStateOuterStateInput));
}
Node* ConvertPlainPrimitiveToNumberOrUndefined(Node* node) {
Node* ConvertPlainPrimitiveToNumber(Node* node) {
DCHECK(NodeProperties::GetType(node)->Is(Type::PlainPrimitive()));
// Avoid inserting too many eager ToNumber() operations.
Reduction const reduction = lowering_->ReduceJSToNumberInput(node);
if (reduction.Changed()) return reduction.replacement();
if (NodeProperties::GetType(node)->Is(Type::NumberOrUndefined())) {
if (NodeProperties::GetType(node)->Is(Type::Number())) {
return node;
}
return graph()->NewNode(simplified()->PlainPrimitiveToNumber(), node);
......@@ -409,16 +409,16 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
return r.ChangeToSpeculativeOperator(
simplified()->SpeculativeNumberAdd(feedback));
}
if (r.BothInputsAre(Type::NumberOrUndefined())) {
if (r.BothInputsAre(Type::Number())) {
// JSAdd(x:number, y:number) => NumberAdd(x, y)
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
r.ConvertInputsToNumberOrUndefined(frame_state);
r.ConvertInputsToNumber(frame_state);
return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number());
}
if (r.NeitherInputCanBe(Type::StringOrReceiver())) {
// JSAdd(x:-string, y:-string) => NumberAdd(ToNumber(x), ToNumber(y))
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
r.ConvertInputsToNumberOrUndefined(frame_state);
r.ConvertInputsToNumber(frame_state);
return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number());
}
if (r.OneInputIs(Type::String())) {
......@@ -468,7 +468,7 @@ Reduction JSTypedLowering::ReduceJSSubtract(Node* node) {
simplified()->SpeculativeNumberSubtract(feedback));
}
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
r.ConvertInputsToNumberOrUndefined(frame_state);
r.ConvertInputsToNumber(frame_state);
return r.ChangeToPureOperator(simplified()->NumberSubtract(), Type::Number());
}
......@@ -476,7 +476,7 @@ Reduction JSTypedLowering::ReduceJSMultiply(Node* node) {
if (flags() & kDisableBinaryOpReduction) return NoChange();
JSBinopReduction r(this, node);
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
r.ConvertInputsToNumberOrUndefined(frame_state);
r.ConvertInputsToNumber(frame_state);
return r.ChangeToPureOperator(simplified()->NumberMultiply(), Type::Number());
}
......@@ -484,7 +484,7 @@ Reduction JSTypedLowering::ReduceJSDivide(Node* node) {
if (flags() & kDisableBinaryOpReduction) return NoChange();
JSBinopReduction r(this, node);
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
r.ConvertInputsToNumberOrUndefined(frame_state);
r.ConvertInputsToNumber(frame_state);
return r.ChangeToPureOperator(simplified()->NumberDivide(), Type::Number());
}
......@@ -494,7 +494,7 @@ Reduction JSTypedLowering::ReduceInt32Binop(Node* node, const Operator* intOp) {
JSBinopReduction r(this, node);
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
r.ConvertInputsToNumberOrUndefined(frame_state);
r.ConvertInputsToNumber(frame_state);
r.ConvertInputsToUI32(kSigned, kSigned);
return r.ChangeToPureOperator(intOp, Type::Integral32());
}
......@@ -507,7 +507,7 @@ Reduction JSTypedLowering::ReduceUI32Shift(Node* node,
JSBinopReduction r(this, node);
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
r.ConvertInputsToNumberOrUndefined(frame_state);
r.ConvertInputsToNumber(frame_state);
r.ConvertInputsToUI32(left_signedness, kUnsigned);
return r.ChangeToPureOperator(shift_op);
}
......@@ -553,7 +553,7 @@ Reduction JSTypedLowering::ReduceJSComparison(Node* node) {
} else {
// TODO(turbofan): mixed signed/unsigned int32 comparisons.
Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
r.ConvertInputsToNumberOrUndefined(frame_state);
r.ConvertInputsToNumber(frame_state);
less_than = simplified()->NumberLessThan();
less_than_or_equal = simplified()->NumberLessThanOrEqual();
}
......@@ -722,7 +722,7 @@ Reduction JSTypedLowering::ReduceJSStrictEqual(Node* node, bool invert) {
if (r.BothInputsAre(Type::String())) {
return r.ChangeToPureOperator(simplified()->StringEqual(), invert);
}
if (r.BothInputsAre(Type::NumberOrUndefined())) {
if (r.BothInputsAre(Type::Number())) {
return r.ChangeToPureOperator(simplified()->NumberEqual(), invert);
}
// TODO(turbofan): js-typed-lowering of StrictEqual(mixed types)
......@@ -1073,7 +1073,7 @@ Reduction JSTypedLowering::ReduceJSStoreProperty(Node* node) {
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Convert to a number first.
if (!value_type->Is(Type::NumberOrUndefined())) {
if (!value_type->Is(Type::Number())) {
Reduction number_reduction = ReduceJSToNumberInput(value);
if (number_reduction.Changed()) {
value = number_reduction.replacement();
......
......@@ -197,11 +197,13 @@
V(NumberClz32) \
V(NumberCeil) \
V(NumberFloor) \
V(NumberFround) \
V(NumberAtan) \
V(NumberAtan2) \
V(NumberLog) \
V(NumberLog1p) \
V(NumberRound) \
V(NumberSqrt) \
V(NumberTrunc) \
V(NumberToInt32) \
V(NumberToUint32) \
......
......@@ -663,10 +663,14 @@ const Operator* RepresentationChanger::Float64OperatorFor(
return machine()->Float64Atan();
case IrOpcode::kNumberAtan2:
return machine()->Float64Atan2();
case IrOpcode::kNumberFround:
return machine()->TruncateFloat64ToFloat32();
case IrOpcode::kNumberLog:
return machine()->Float64Log();
case IrOpcode::kNumberLog1p:
return machine()->Float64Log1p();
case IrOpcode::kNumberSqrt:
return machine()->Float64Sqrt();
case IrOpcode::kNumberSilenceNaN:
return machine()->Float64SilenceNaN();
default:
......
......@@ -611,16 +611,17 @@ class RepresentationSelector {
Type* GetUpperBound(Node* node) { return NodeProperties::GetType(node); }
bool InputIs(Node* node, Type* type) {
DCHECK_EQ(1, node->InputCount());
return GetUpperBound(node->InputAt(0))->Is(type);
}
bool BothInputsAreSigned32(Node* node) {
DCHECK_EQ(2, node->InputCount());
return GetUpperBound(node->InputAt(0))->Is(Type::Signed32()) &&
GetUpperBound(node->InputAt(1))->Is(Type::Signed32());
return BothInputsAre(node, Type::Signed32());
}
bool BothInputsAreUnsigned32(Node* node) {
DCHECK_EQ(2, node->InputCount());
return GetUpperBound(node->InputAt(0))->Is(Type::Unsigned32()) &&
GetUpperBound(node->InputAt(1))->Is(Type::Unsigned32());
return BothInputsAre(node, Type::Unsigned32());
}
bool BothInputsAre(Node* node, Type* type) {
......@@ -1393,6 +1394,12 @@ class RepresentationSelector {
if (lower()) DeferReplacement(node, lowering->Float64Floor(node));
return;
}
case IrOpcode::kNumberFround: {
VisitUnop(node, UseInfo::TruncatingFloat64(),
MachineRepresentation::kFloat32);
if (lower()) NodeProperties::ChangeOp(node, Float64Op(node));
return;
}
case IrOpcode::kNumberAtan2: {
VisitBinop(node, UseInfo::TruncatingFloat64(),
MachineRepresentation::kFloat64);
......@@ -1413,6 +1420,12 @@ class RepresentationSelector {
if (lower()) DeferReplacement(node, lowering->Float64Round(node));
return;
}
case IrOpcode::kNumberSqrt: {
VisitUnop(node, UseInfo::TruncatingFloat64(),
MachineRepresentation::kFloat64);
if (lower()) NodeProperties::ChangeOp(node, Float64Op(node));
return;
}
case IrOpcode::kNumberTrunc: {
VisitUnop(node, UseInfo::TruncatingFloat64(),
MachineRepresentation::kFloat64);
......@@ -1683,21 +1696,36 @@ class RepresentationSelector {
return;
}
case IrOpcode::kPlainPrimitiveToNumber:
ProcessInput(node, 0, UseInfo::AnyTagged());
if (truncation.TruncatesToWord32()) {
SetOutput(node, MachineRepresentation::kWord32);
if (lower()) {
NodeProperties::ChangeOp(node,
simplified()->PlainPrimitiveToWord32());
// TODO(jarin): Extend this to Number \/ Oddball
if (InputIs(node, Type::NumberOrUndefined())) {
VisitUnop(node, UseInfo::TruncatingWord32(),
MachineRepresentation::kWord32);
if (lower()) DeferReplacement(node, node->InputAt(0));
} else {
VisitUnop(node, UseInfo::AnyTagged(),
MachineRepresentation::kWord32);
if (lower()) {
NodeProperties::ChangeOp(node,
simplified()->PlainPrimitiveToWord32());
}
}
} else if (truncation.TruncatesToFloat64()) {
SetOutput(node, MachineRepresentation::kFloat64);
if (lower()) {
NodeProperties::ChangeOp(node,
simplified()->PlainPrimitiveToFloat64());
// TODO(jarin): Extend this to Number \/ Oddball
if (InputIs(node, Type::NumberOrUndefined())) {
VisitUnop(node, UseInfo::TruncatingFloat64(),
MachineRepresentation::kFloat64);
if (lower()) DeferReplacement(node, node->InputAt(0));
} else {
VisitUnop(node, UseInfo::AnyTagged(),
MachineRepresentation::kFloat64);
if (lower()) {
NodeProperties::ChangeOp(node,
simplified()->PlainPrimitiveToFloat64());
}
}
} else {
SetOutput(node, MachineRepresentation::kTagged);
VisitUnop(node, UseInfo::AnyTagged(), MachineRepresentation::kTagged);
}
return;
case IrOpcode::kObjectIsCallable:
......
......@@ -204,11 +204,13 @@ BinaryOperationHints::Hint BinaryOperationHintOf(const Operator* op) {
V(NumberClz32, Operator::kNoProperties, 1) \
V(NumberCeil, Operator::kNoProperties, 1) \
V(NumberFloor, Operator::kNoProperties, 1) \
V(NumberFround, Operator::kNoProperties, 1) \
V(NumberAtan, Operator::kNoProperties, 1) \
V(NumberAtan2, Operator::kNoProperties, 2) \
V(NumberLog, Operator::kNoProperties, 1) \
V(NumberLog1p, Operator::kNoProperties, 1) \
V(NumberRound, Operator::kNoProperties, 1) \
V(NumberSqrt, Operator::kNoProperties, 1) \
V(NumberTrunc, Operator::kNoProperties, 1) \
V(NumberToInt32, Operator::kNoProperties, 1) \
V(NumberToUint32, Operator::kNoProperties, 1) \
......@@ -217,9 +219,9 @@ BinaryOperationHints::Hint BinaryOperationHintOf(const Operator* op) {
V(NumberConvertHoleNaN, Operator::kNoProperties, 1) \
V(StringFromCharCode, Operator::kNoProperties, 1) \
V(StringToNumber, Operator::kNoProperties, 1) \
V(PlainPrimitiveToNumber, Operator::kNoWrite, 1) \
V(PlainPrimitiveToWord32, Operator::kNoWrite, 1) \
V(PlainPrimitiveToFloat64, Operator::kNoWrite, 1) \
V(PlainPrimitiveToNumber, Operator::kNoProperties, 1) \
V(PlainPrimitiveToWord32, Operator::kNoProperties, 1) \
V(PlainPrimitiveToFloat64, Operator::kNoProperties, 1) \
V(ChangeTaggedSignedToInt32, Operator::kNoProperties, 1) \
V(ChangeTaggedToInt32, Operator::kNoProperties, 1) \
V(ChangeTaggedToUint32, Operator::kNoProperties, 1) \
......
......@@ -154,11 +154,13 @@ class SimplifiedOperatorBuilder final : public ZoneObject {
const Operator* NumberClz32();
const Operator* NumberCeil();
const Operator* NumberFloor();
const Operator* NumberFround();
const Operator* NumberAtan();
const Operator* NumberAtan2();
const Operator* NumberLog();
const Operator* NumberLog1p();
const Operator* NumberRound();
const Operator* NumberSqrt();
const Operator* NumberTrunc();
const Operator* NumberToInt32();
const Operator* NumberToUint32();
......
......@@ -1788,6 +1788,8 @@ Type* Typer::Visitor::TypeNumberFloor(Node* node) {
return TypeUnaryOp(node, NumberFloor);
}
Type* Typer::Visitor::TypeNumberFround(Node* node) { return Type::Number(); }
Type* Typer::Visitor::TypeNumberAtan(Node* node) { return Type::Number(); }
Type* Typer::Visitor::TypeNumberAtan2(Node* node) { return Type::Number(); }
......@@ -1800,6 +1802,8 @@ Type* Typer::Visitor::TypeNumberRound(Node* node) {
return TypeUnaryOp(node, NumberRound);
}
Type* Typer::Visitor::TypeNumberSqrt(Node* node) { return Type::Number(); }
Type* Typer::Visitor::TypeNumberTrunc(Node* node) {
return TypeUnaryOp(node, NumberTrunc);
}
......@@ -1902,7 +1906,7 @@ Type* Typer::Visitor::TypeChangeTaggedToFloat64(Node* node) {
Type* Typer::Visitor::TypeTruncateTaggedToFloat64(Node* node) {
Type* arg = Operand(node, 0);
DCHECK(arg->Is(Type::NumberOrUndefined()));
DCHECK(arg->Is(Type::NumberOrOddball()));
return ChangeRepresentation(arg, Type::UntaggedFloat64(), zone());
}
......
......@@ -675,16 +675,16 @@ void Verifier::Visitor::Check(Node* node) {
CheckUpperIs(node, Type::Number());
break;
case IrOpcode::kNumberEqual:
// (NumberOrUndefined, NumberOrUndefined) -> Boolean
CheckValueInputIs(node, 0, Type::NumberOrUndefined());
CheckValueInputIs(node, 1, Type::NumberOrUndefined());
// (Number, Number) -> Boolean
CheckValueInputIs(node, 0, Type::Number());
CheckValueInputIs(node, 1, Type::Number());
CheckUpperIs(node, Type::Boolean());
break;
case IrOpcode::kNumberLessThan:
case IrOpcode::kNumberLessThanOrEqual:
// (Number, Number) -> Boolean
CheckValueInputIs(node, 0, Type::NumberOrUndefined());
CheckValueInputIs(node, 1, Type::NumberOrUndefined());
CheckValueInputIs(node, 0, Type::Number());
CheckValueInputIs(node, 1, Type::Number());
CheckUpperIs(node, Type::Boolean());
break;
case IrOpcode::kSpeculativeNumberAdd:
......@@ -695,16 +695,15 @@ void Verifier::Visitor::Check(Node* node) {
case IrOpcode::kNumberMultiply:
case IrOpcode::kNumberDivide:
// (Number, Number) -> Number
CheckValueInputIs(node, 0, Type::NumberOrUndefined());
CheckValueInputIs(node, 1, Type::NumberOrUndefined());
// CheckUpperIs(node, Type::Number());
CheckValueInputIs(node, 0, Type::Number());
CheckValueInputIs(node, 1, Type::Number());
CheckUpperIs(node, Type::Number());
break;
case IrOpcode::kNumberModulus:
// (Number, Number) -> Number
CheckValueInputIs(node, 0, Type::Number());
CheckValueInputIs(node, 1, Type::Number());
// TODO(rossberg): activate once we retype after opcode changes.
// CheckUpperIs(node, Type::Number());
CheckUpperIs(node, Type::Number());
break;
case IrOpcode::kNumberBitwiseOr:
case IrOpcode::kNumberBitwiseXor:
......@@ -746,10 +745,12 @@ void Verifier::Visitor::Check(Node* node) {
break;
case IrOpcode::kNumberCeil:
case IrOpcode::kNumberFloor:
case IrOpcode::kNumberFround:
case IrOpcode::kNumberAtan:
case IrOpcode::kNumberLog:
case IrOpcode::kNumberLog1p:
case IrOpcode::kNumberRound:
case IrOpcode::kNumberSqrt:
case IrOpcode::kNumberTrunc:
// Number -> Number
CheckValueInputIs(node, 0, Type::Number());
......@@ -757,12 +758,12 @@ void Verifier::Visitor::Check(Node* node) {
break;
case IrOpcode::kNumberToInt32:
// Number -> Signed32
CheckValueInputIs(node, 0, Type::NumberOrUndefined());
CheckValueInputIs(node, 0, Type::Number());
CheckUpperIs(node, Type::Signed32());
break;
case IrOpcode::kNumberToUint32:
// Number -> Unsigned32
CheckValueInputIs(node, 0, Type::NumberOrUndefined());
CheckValueInputIs(node, 0, Type::Number());
CheckUpperIs(node, Type::Unsigned32());
break;
case IrOpcode::kNumberIsHoleNaN:
......@@ -851,7 +852,7 @@ void Verifier::Visitor::Check(Node* node) {
break;
}
case IrOpcode::kChangeTaggedToFloat64: {
// Number /\ Tagged -> Number /\ UntaggedFloat64
// NumberOrUndefined /\ Tagged -> Number /\ UntaggedFloat64
// TODO(neis): Activate once ChangeRepresentation works in typer.
// Type* from = Type::Intersect(Type::Number(), Type::Tagged());
// Type* to = Type::Intersect(Type::Number(), Type::UntaggedFloat64());
......@@ -970,7 +971,7 @@ void Verifier::Visitor::Check(Node* node) {
CheckNotTyped(node);
break;
case IrOpcode::kNumberSilenceNaN:
CheckValueInputIs(node, 0, Type::NumberOrUndefined());
CheckValueInputIs(node, 0, Type::Number());
CheckUpperIs(node, Type::Number());
break;
......
......@@ -219,6 +219,7 @@ namespace internal {
V(BooleanOrNullOrUndefined, kBoolean | kNull | kUndefined) \
V(NullOrUndefined, kNull | kUndefined) \
V(Undetectable, kNullOrUndefined | kOtherUndetectable) \
V(NumberOrOddball, kNumber | kNullOrUndefined | kBoolean) \
V(NumberOrSimdOrString, kNumber | kSimd | kString) \
V(NumberOrString, kNumber | kString) \
V(NumberOrUndefined, kNumber | kUndefined) \
......
......@@ -2311,8 +2311,15 @@ IS_UNOP_MATCHER(Float64RoundTiesAway)
IS_UNOP_MATCHER(Float64ExtractLowWord32)
IS_UNOP_MATCHER(Float64ExtractHighWord32)
IS_UNOP_MATCHER(NumberAtan)
IS_UNOP_MATCHER(NumberCeil)
IS_UNOP_MATCHER(NumberClz32)
IS_UNOP_MATCHER(NumberFloor)
IS_UNOP_MATCHER(NumberFround)
IS_UNOP_MATCHER(NumberLog)
IS_UNOP_MATCHER(NumberLog1p)
IS_UNOP_MATCHER(NumberRound)
IS_UNOP_MATCHER(NumberSqrt)
IS_UNOP_MATCHER(NumberTrunc)
IS_UNOP_MATCHER(NumberToInt32)
IS_UNOP_MATCHER(NumberToUint32)
IS_UNOP_MATCHER(PlainPrimitiveToNumber)
......
......@@ -227,8 +227,15 @@ Matcher<Node*> IsNumberImul(const Matcher<Node*>& lhs_matcher,
Matcher<Node*> IsNumberAtan(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberAtan2(const Matcher<Node*>& lhs_matcher,
const Matcher<Node*>& rhs_matcher);
Matcher<Node*> IsNumberCeil(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberClz32(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberFloor(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberFround(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberLog(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberLog1p(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberRound(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberSqrt(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberTrunc(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsStringFromCharCode(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsAllocate(const Matcher<Node*>& size_matcher,
const Matcher<Node*>& effect_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