Revert "[turbofan] Improve equality on NumberOrOddball"

This reverts commit 6204768bab.

Reason for revert: A number of Clusterfuzz reports (e.g. https://bugs.chromium.org/p/chromium/issues/detail?id=1079474)

Original change's description:
> [turbofan] Improve equality on NumberOrOddball
> 
> This CL cleans up CompareOperationFeedback by replacing it with a
> composable set of flags. The interpreter is changed to collect
> more specific feedback for abstract equality, especially if oddballs
> are involved.
> 
> TurboFan is changed to construct SpeculativeNumberEqual operator
> instead of the generic JSEqual in many more cases. This change has
> shown a local speedup of a factor of 3-10, because the specific
> operator is way faster than calling into the generic builtin, but
> it also enables additional optimizations, further improving
> runtime performance.
> 
> Bug: v8:5660
> Change-Id: I856752caa707e9a4f742c6e7a9c75552fb431d28
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2162854
> Reviewed-by: Mythri Alle <mythria@chromium.org>
> Reviewed-by: Georg Neis <neis@chromium.org>
> Commit-Queue: Nico Hartmann <nicohartmann@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#67645}

TBR=rmcilroy@chromium.org,neis@chromium.org,mythria@chromium.org,nicohartmann@chromium.org

Change-Id: I3410310ed2b1ff2eaee70c1b91c3151d35866108
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: v8:5660
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2190414
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Commit-Queue: Nico Hartmann <nicohartmann@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67673}
This commit is contained in:
Nico Hartmann 2020-05-08 09:15:51 +00:00 committed by Commit Bot
parent f701df1f3c
commit f4b98cc654
8 changed files with 143 additions and 287 deletions

View File

@ -11171,8 +11171,7 @@ void CodeStubAssembler::GenerateEqual_Same(SloppyTNode<Object> value,
BIND(&if_boolean);
{
CombineFeedback(var_type_feedback,
CompareOperationFeedback::kNumberOrOddball);
CombineFeedback(var_type_feedback, CompareOperationFeedback::kAny);
Goto(if_equal);
}
@ -11254,54 +11253,50 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
BIND(&if_left_smi);
{
Label if_right_smi(this), if_right_not_smi(this);
CombineFeedback(var_type_feedback,
CompareOperationFeedback::kSignedSmall);
Branch(TaggedIsSmi(right), &if_right_smi, &if_right_not_smi);
BIND(&if_right_smi);
{
// We have already checked for {left} and {right} being the same value,
// so when we get here they must be different Smis.
CombineFeedback(var_type_feedback,
CompareOperationFeedback::kSignedSmall);
Goto(&if_notequal);
}
BIND(&if_right_not_smi);
{
TNode<Map> right_map = LoadMap(CAST(right));
Label if_right_heapnumber(this), if_right_oddball(this),
Label if_right_heapnumber(this), if_right_boolean(this),
if_right_bigint(this, Label::kDeferred),
if_right_receiver(this, Label::kDeferred);
GotoIf(IsHeapNumberMap(right_map), &if_right_heapnumber);
// {left} is Smi and {right} is not HeapNumber or Smi.
if (var_type_feedback != nullptr) {
*var_type_feedback = SmiConstant(CompareOperationFeedback::kAny);
}
GotoIf(IsBooleanMap(right_map), &if_right_boolean);
TNode<Uint16T> right_type = LoadMapInstanceType(right_map);
GotoIf(IsStringInstanceType(right_type), &do_right_stringtonumber);
GotoIf(IsOddballInstanceType(right_type), &if_right_oddball);
GotoIf(IsBigIntInstanceType(right_type), &if_right_bigint);
GotoIf(IsJSReceiverInstanceType(right_type), &if_right_receiver);
CombineFeedback(var_type_feedback, CompareOperationFeedback::kAny);
Goto(&if_notequal);
Branch(IsJSReceiverInstanceType(right_type), &if_right_receiver,
&if_notequal);
BIND(&if_right_heapnumber);
{
CombineFeedback(var_type_feedback, CompareOperationFeedback::kNumber);
var_left_float = SmiToFloat64(CAST(left));
var_right_float = LoadHeapNumberValue(CAST(right));
CombineFeedback(var_type_feedback, CompareOperationFeedback::kNumber);
Goto(&do_float_comparison);
}
BIND(&if_right_oddball);
BIND(&if_right_boolean);
{
CombineFeedback(var_type_feedback,
CompareOperationFeedback::kOddball);
GotoIfNot(IsBooleanMap(right_map), &if_notequal);
var_right = LoadObjectField(CAST(right), Oddball::kToNumberOffset);
Goto(&loop);
}
BIND(&if_right_bigint);
{
CombineFeedback(var_type_feedback, CompareOperationFeedback::kBigInt);
result = CAST(CallRuntime(Runtime::kBigIntEqualToNumber,
NoContextConstant(), right, left));
Goto(&end);
@ -11309,14 +11304,11 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
BIND(&if_right_receiver);
{
CombineFeedback(var_type_feedback,
CompareOperationFeedback::kReceiver);
Callable callable = CodeFactory::NonPrimitiveToPrimitive(isolate());
var_right = CallStub(callable, context, right);
Goto(&loop);
}
}
}
BIND(&if_left_not_smi);
{
@ -11353,30 +11345,27 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
BIND(&if_left_number);
{
Label if_right_not_number(this);
CombineFeedback(var_type_feedback, CompareOperationFeedback::kNumber);
GotoIf(Word32NotEqual(left_type, right_type), &if_right_not_number);
var_left_float = LoadHeapNumberValue(CAST(left));
var_right_float = LoadHeapNumberValue(CAST(right));
CombineFeedback(var_type_feedback, CompareOperationFeedback::kNumber);
Goto(&do_float_comparison);
BIND(&if_right_not_number);
{
Label if_right_oddball(this);
Label if_right_boolean(this);
if (var_type_feedback != nullptr) {
*var_type_feedback = SmiConstant(CompareOperationFeedback::kAny);
}
GotoIf(IsStringInstanceType(right_type), &do_right_stringtonumber);
GotoIf(IsOddballInstanceType(right_type), &if_right_oddball);
GotoIf(IsBooleanMap(right_map), &if_right_boolean);
GotoIf(IsBigIntInstanceType(right_type), &use_symmetry);
GotoIf(IsJSReceiverInstanceType(right_type), &use_symmetry);
CombineFeedback(var_type_feedback, CompareOperationFeedback::kAny);
Goto(&if_notequal);
Branch(IsJSReceiverInstanceType(right_type), &use_symmetry,
&if_notequal);
BIND(&if_right_oddball);
BIND(&if_right_boolean);
{
CombineFeedback(var_type_feedback,
CompareOperationFeedback::kOddball);
GotoIfNot(IsBooleanMap(right_map), &if_notequal);
var_right = LoadObjectField(CAST(right), Oddball::kToNumberOffset);
Goto(&loop);
}
@ -11387,8 +11376,6 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
{
Label if_right_heapnumber(this), if_right_bigint(this),
if_right_string(this), if_right_boolean(this);
CombineFeedback(var_type_feedback, CompareOperationFeedback::kBigInt);
GotoIf(IsHeapNumberMap(right_map), &if_right_heapnumber);
GotoIf(IsBigIntInstanceType(right_type), &if_right_bigint);
GotoIf(IsStringInstanceType(right_type), &if_right_string);
@ -11398,7 +11385,9 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
BIND(&if_right_heapnumber);
{
CombineFeedback(var_type_feedback, CompareOperationFeedback::kNumber);
if (var_type_feedback != nullptr) {
*var_type_feedback = SmiConstant(CompareOperationFeedback::kAny);
}
result = CAST(CallRuntime(Runtime::kBigIntEqualToNumber,
NoContextConstant(), left, right));
Goto(&end);
@ -11406,7 +11395,7 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
BIND(&if_right_bigint);
{
// We already have BigInt feedback.
CombineFeedback(var_type_feedback, CompareOperationFeedback::kBigInt);
result = CAST(CallRuntime(Runtime::kBigIntEqualToBigInt,
NoContextConstant(), left, right));
Goto(&end);
@ -11414,7 +11403,9 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
BIND(&if_right_string);
{
CombineFeedback(var_type_feedback, CompareOperationFeedback::kString);
if (var_type_feedback != nullptr) {
*var_type_feedback = SmiConstant(CompareOperationFeedback::kAny);
}
result = CAST(CallRuntime(Runtime::kBigIntEqualToString,
NoContextConstant(), left, right));
Goto(&end);
@ -11422,8 +11413,9 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
BIND(&if_right_boolean);
{
CombineFeedback(var_type_feedback,
CompareOperationFeedback::kBoolean);
if (var_type_feedback != nullptr) {
*var_type_feedback = SmiConstant(CompareOperationFeedback::kAny);
}
var_right = LoadObjectField(CAST(right), Oddball::kToNumberOffset);
Goto(&loop);
}
@ -11438,42 +11430,29 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
{
// {left} is either Null or Undefined. Check if {right} is
// undetectable (which includes Null and Undefined).
Label if_right_undetectable(this), if_right_number_or_oddball(this),
if_right_not_number_or_oddball_or_undetectable(this);
GotoIf(IsUndetectableMap(right_map), &if_right_undetectable);
GotoIf(IsHeapNumberInstanceType(right_type),
&if_right_number_or_oddball);
GotoIf(IsOddballInstanceType(right_type),
&if_right_number_or_oddball);
Goto(&if_right_not_number_or_oddball_or_undetectable);
Label if_right_undetectable(this), if_right_not_undetectable(this);
Branch(IsUndetectableMap(right_map), &if_right_undetectable,
&if_right_not_undetectable);
BIND(&if_right_undetectable);
{
if (var_type_feedback != nullptr) {
// If {right} is undetectable, it must be either also
// Null or Undefined, or a Receiver (aka document.all).
CombineFeedback(
var_type_feedback,
*var_type_feedback = SmiConstant(
CompareOperationFeedback::kReceiverOrNullOrUndefined);
}
Goto(&if_equal);
}
BIND(&if_right_number_or_oddball);
{
CombineFeedback(var_type_feedback,
CompareOperationFeedback::kNumberOrOddball);
Goto(&if_notequal);
}
BIND(&if_right_not_number_or_oddball_or_undetectable);
BIND(&if_right_not_undetectable);
{
if (var_type_feedback != nullptr) {
// Track whether {right} is Null, Undefined or Receiver.
CombineFeedback(
var_type_feedback,
*var_type_feedback = SmiConstant(
CompareOperationFeedback::kReceiverOrNullOrUndefined);
GotoIf(IsJSReceiverInstanceType(right_type), &if_notequal);
CombineFeedback(var_type_feedback,
CompareOperationFeedback::kAny);
*var_type_feedback = SmiConstant(CompareOperationFeedback::kAny);
}
Goto(&if_notequal);
}
@ -11481,8 +11460,9 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
BIND(&if_left_boolean);
{
CombineFeedback(var_type_feedback,
CompareOperationFeedback::kBoolean);
if (var_type_feedback != nullptr) {
*var_type_feedback = SmiConstant(CompareOperationFeedback::kAny);
}
// If {right} is a Boolean too, it must be a different Boolean.
GotoIf(TaggedEqual(right_map, left_map), &if_notequal);
@ -11565,7 +11545,9 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
{
// {right} is a Primitive, and neither Null or Undefined;
// convert {left} to Primitive too.
CombineFeedback(var_type_feedback, CompareOperationFeedback::kAny);
if (var_type_feedback != nullptr) {
*var_type_feedback = SmiConstant(CompareOperationFeedback::kAny);
}
Callable callable = CodeFactory::NonPrimitiveToPrimitive(isolate());
var_left = CallStub(callable, context, left);
Goto(&loop);
@ -11576,12 +11558,6 @@ TNode<Oddball> CodeStubAssembler::Equal(SloppyTNode<Object> left,
BIND(&do_right_stringtonumber);
{
if (var_type_feedback != nullptr) {
TNode<Map> right_map = LoadMap(CAST(right));
TNode<Uint16T> right_type = LoadMapInstanceType(right_map);
CombineFeedback(var_type_feedback,
CollectFeedbackForString(right_type));
}
var_right = CallBuiltin(Builtins::kStringToNumber, context, right);
Goto(&loop);
}
@ -11860,48 +11836,16 @@ TNode<Oddball> CodeStubAssembler::StrictEqual(
BIND(&if_lhsisoddball);
{
Label if_lhsisboolean(this), if_lhsisnotboolean(this);
Branch(IsBooleanMap(lhs_map), &if_lhsisboolean,
&if_lhsisnotboolean);
BIND(&if_lhsisboolean);
{
OverwriteFeedback(var_type_feedback,
CompareOperationFeedback::kNumberOrOddball);
GotoIf(IsBooleanMap(rhs_map), &if_notequal);
Goto(&if_not_equivalent_types);
}
BIND(&if_lhsisnotboolean);
{
Label if_rhsisheapnumber(this), if_rhsisnotheapnumber(this);
STATIC_ASSERT(LAST_PRIMITIVE_HEAP_OBJECT_TYPE ==
ODDBALL_TYPE);
STATIC_ASSERT(LAST_PRIMITIVE_HEAP_OBJECT_TYPE == ODDBALL_TYPE);
GotoIf(IsBooleanMap(rhs_map), &if_not_equivalent_types);
GotoIf(Int32LessThan(rhs_instance_type,
Int32Constant(ODDBALL_TYPE)),
&if_not_equivalent_types);
Branch(IsHeapNumberMap(rhs_map), &if_rhsisheapnumber,
&if_rhsisnotheapnumber);
BIND(&if_rhsisheapnumber);
{
OverwriteFeedback(
var_type_feedback,
CompareOperationFeedback::kNumberOrOddball);
Goto(&if_not_equivalent_types);
}
BIND(&if_rhsisnotheapnumber);
{
OverwriteFeedback(
var_type_feedback,
CompareOperationFeedback::kReceiverOrNullOrUndefined);
Goto(&if_notequal);
}
}
}
BIND(&if_lhsissymbol);
{
@ -11956,14 +11900,7 @@ TNode<Oddball> CodeStubAssembler::StrictEqual(
}
BIND(&if_rhsisnotnumber);
{
TNode<Uint16T> rhs_instance_type = LoadMapInstanceType(rhs_map);
GotoIfNot(IsOddballInstanceType(rhs_instance_type),
&if_not_equivalent_types);
OverwriteFeedback(var_type_feedback,
CompareOperationFeedback::kNumberOrOddball);
Goto(&if_notequal);
}
Goto(&if_not_equivalent_types);
}
}
}

View File

@ -1348,46 +1348,31 @@ class BinaryOperationFeedback {
};
// Type feedback is encoded in such a way that, we can combine the feedback
// at different points by performing an 'OR' operation.
// at different points by performing an 'OR' operation. Type feedback moves
// to a more generic type when we combine feedback.
//
// kSignedSmall -> kNumber -> kNumberOrOddball -> kAny
// kReceiver -> kReceiverOrNullOrUndefined -> kAny
// kInternalizedString -> kString -> kAny
// kSymbol -> kAny
// kBigInt -> kAny
//
// This is distinct from BinaryOperationFeedback on purpose, because the
// feedback that matters differs greatly as well as the way it is consumed.
class CompareOperationFeedback {
enum {
kSignedSmallFlag = 1 << 0,
kOtherNumberFlag = 1 << 1,
kBooleanFlag = 1 << 2,
kOtherOddballFlag = 1 << 3,
kNullOrUndefinedFlag = 1 << 4,
kInternalizedStringFlag = 1 << 5,
kOtherStringFlag = 1 << 6,
kSymbolFlag = 1 << 7,
kBigIntFlag = 1 << 8,
kReceiverFlag = 1 << 9,
kAnyMask = 0x3FF,
};
public:
enum Type {
kNone = 0,
kBoolean = kBooleanFlag,
kNullOrUndefined = kNullOrUndefinedFlag,
kOddball = kBoolean | kOtherOddballFlag | kNullOrUndefined,
kSignedSmall = kSignedSmallFlag,
kNumber = kSignedSmall | kOtherNumberFlag,
kNumberOrOddball = kNumber | kOddball,
kInternalizedString = kInternalizedStringFlag,
kString = kInternalizedString | kOtherStringFlag,
kReceiver = kReceiverFlag,
kReceiverOrNullOrUndefined = kReceiver | kNullOrUndefined,
kBigInt = kBigIntFlag,
kSymbol = kSymbolFlag,
kAny = kAnyMask,
enum {
kNone = 0x000,
kSignedSmall = 0x001,
kNumber = 0x003,
kNumberOrOddball = 0x007,
kInternalizedString = 0x008,
kString = 0x018,
kSymbol = 0x020,
kBigInt = 0x040,
kReceiver = 0x080,
kReceiverOrNullOrUndefined = 0x180,
kAny = 0x1ff
};
};

View File

@ -1393,7 +1393,6 @@ void CodeAssemblerLabel::MergeVariables() {
}
// If the following asserts, then you've jumped to a label without a bound
// variable along that path that expects to merge its value into a phi.
// This can also occur if a label is bound that is never jumped to.
DCHECK(variable_phis_.find(var) == variable_phis_.end() ||
count == merge_count_);
USE(count);

View File

@ -887,12 +887,7 @@ Reduction JSTypedLowering::ReduceJSStrictEqual(Node* node) {
if (r.BothInputsAre(Type::Signed32()) ||
r.BothInputsAre(Type::Unsigned32())) {
return r.ChangeToPureOperator(simplified()->NumberEqual());
} else if (r.GetCompareNumberOperationHint(&hint) &&
hint != NumberOperationHint::kNumberOrOddball) {
// SpeculativeNumberEqual[kNumberOrOddball] performs implicit conversion
// of oddballs to numbers, so we must not generate it for strict equality.
DCHECK(hint == NumberOperationHint::kNumber ||
hint == NumberOperationHint::kSignedSmall);
} else if (r.GetCompareNumberOperationHint(&hint)) {
return r.ChangeToSpeculativeOperator(
simplified()->SpeculativeNumberEqual(hint), Type::Boolean());
} else if (r.BothInputsAre(Type::Number())) {

View File

@ -2054,6 +2054,11 @@ class RepresentationSelector {
// This doesn't make sense for compare operations.
UNREACHABLE();
case NumberOperationHint::kNumberOrOddball:
// Abstract and strict equality don't perform ToNumber conversions
// on Oddballs, so make sure we don't accidentially sneak in a
// hint with Oddball feedback here.
DCHECK_NE(IrOpcode::kSpeculativeNumberEqual, node->opcode());
V8_FALLTHROUGH;
case NumberOperationHint::kNumber:
VisitBinop<T>(node,
CheckedUseInfoAsFloat64FromHint(

View File

@ -233,48 +233,33 @@ BinaryOperationHint BinaryOperationHintFromFeedback(int type_feedback) {
}
// Helper function to transform the feedback to CompareOperationHint.
template <CompareOperationFeedback::Type Feedback>
bool Is(int type_feedback) {
return !(type_feedback & ~Feedback);
}
CompareOperationHint CompareOperationHintFromFeedback(int type_feedback) {
if (Is<CompareOperationFeedback::kNone>(type_feedback)) {
switch (type_feedback) {
case CompareOperationFeedback::kNone:
return CompareOperationHint::kNone;
}
if (Is<CompareOperationFeedback::kSignedSmall>(type_feedback)) {
case CompareOperationFeedback::kSignedSmall:
return CompareOperationHint::kSignedSmall;
} else if (Is<CompareOperationFeedback::kNumber>(type_feedback)) {
case CompareOperationFeedback::kNumber:
return CompareOperationHint::kNumber;
} else if (Is<CompareOperationFeedback::kNumberOrOddball>(type_feedback)) {
case CompareOperationFeedback::kNumberOrOddball:
return CompareOperationHint::kNumberOrOddball;
}
if (Is<CompareOperationFeedback::kInternalizedString>(type_feedback)) {
case CompareOperationFeedback::kInternalizedString:
return CompareOperationHint::kInternalizedString;
} else if (Is<CompareOperationFeedback::kString>(type_feedback)) {
case CompareOperationFeedback::kString:
return CompareOperationHint::kString;
}
if (Is<CompareOperationFeedback::kReceiver>(type_feedback)) {
return CompareOperationHint::kReceiver;
} else if (Is<CompareOperationFeedback::kReceiverOrNullOrUndefined>(
type_feedback)) {
return CompareOperationHint::kReceiverOrNullOrUndefined;
}
if (Is<CompareOperationFeedback::kBigInt>(type_feedback)) {
return CompareOperationHint::kBigInt;
}
if (Is<CompareOperationFeedback::kSymbol>(type_feedback)) {
case CompareOperationFeedback::kSymbol:
return CompareOperationHint::kSymbol;
}
DCHECK(Is<CompareOperationFeedback::kAny>(type_feedback));
case CompareOperationFeedback::kBigInt:
return CompareOperationHint::kBigInt;
case CompareOperationFeedback::kReceiver:
return CompareOperationHint::kReceiver;
case CompareOperationFeedback::kReceiverOrNullOrUndefined:
return CompareOperationHint::kReceiverOrNullOrUndefined;
default:
return CompareOperationHint::kAny;
}
UNREACHABLE();
}
// Helper function to transform the feedback to ForInHint.
ForInHint ForInHintFromFeedback(int type_feedback) {

View File

@ -2087,6 +2087,7 @@ TEST(InterpreterMixedComparisons) {
LoadStringAndAddSpace(&builder, &ast_factory, rhs_cstr,
string_add_slot);
}
break;
} else {
CHECK_EQ(which_side, kLhsIsString);
// Comparison with String on the lhs and HeapNumber on the rhs.
@ -2120,15 +2121,6 @@ TEST(InterpreterMixedComparisons) {
if (tester.HasFeedbackMetadata()) {
MaybeObject feedback = callable.vector().Get(slot);
CHECK(feedback->IsSmi());
if (kComparisonTypes[c] == Token::Value::EQ) {
// For sloppy equality, we have more precise feedback.
CHECK_EQ(
CompareOperationFeedback::kNumber |
(string_type == kInternalizedStringConstant
? CompareOperationFeedback::kInternalizedString
: CompareOperationFeedback::kString),
feedback->ToSmi().value());
} else {
// Comparison with a number and string collects kAny feedback.
CHECK_EQ(CompareOperationFeedback::kAny,
feedback->ToSmi().value());
@ -2139,7 +2131,6 @@ TEST(InterpreterMixedComparisons) {
}
}
}
}
TEST(InterpreterStrictNotEqual) {
HandleAndZoneScope handles;

View File

@ -1,41 +0,0 @@
// Copyright 2020 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.
// Flags: --opt --allow-natives-syntax
(function() {
function test(a, b) {
return a === b;
}
%PrepareFunctionForOptimization(test);
assertTrue(test(undefined, undefined));
assertTrue(test(undefined, undefined));
%OptimizeFunctionOnNextCall(test);
assertTrue(test(undefined, undefined));
})();
(function() {
function test(a, b) {
return a === b;
}
%PrepareFunctionForOptimization(test);
assertTrue(test(true, true));
assertTrue(test(true, true));
%OptimizeFunctionOnNextCall(test);
assertFalse(test(true, 1));
})();
(function() {
function test(a, b) {
return a == b;
}
%PrepareFunctionForOptimization(test);
assertTrue(test(true, true));
assertTrue(test(true, true));
%OptimizeFunctionOnNextCall(test);
assertTrue(test(true, 1));
})();