AOT support destructuring assignment

Issues: https://gitee.com/openharmony/arkcompiler_ets_runtime/issues/I95QHW?from=project-issue
Signed-off-by: liuzhijie <liuzhijie9@huawei.com>

Change-Id: I329953ca606df89697f0ce0aed0b5bc9a5bd3f4d
This commit is contained in:
liuzhijie 2024-03-01 11:16:32 +08:00
parent 3655959dfb
commit 60989eb7eb
25 changed files with 150 additions and 23 deletions

View File

@ -1841,7 +1841,8 @@ void Builtins::InitializeIterator(const JSHandle<GlobalEnv> &env, const JSHandle
// Iterator.prototype.next()
SetFunction(env, iteratorPrototype, "next", BuiltinsIterator::Next, FunctionLength::ONE);
// Iterator.prototype.return()
SetFunction(env, iteratorPrototype, "return", BuiltinsIterator::Return, FunctionLength::ONE);
SetFunction(env, iteratorPrototype, "return", BuiltinsIterator::Return, FunctionLength::ONE,
BUILTINS_STUB_ID(ITERATOR_PROTO_RETURN));
// Iterator.prototype.throw()
SetFunction(env, iteratorPrototype, "throw", BuiltinsIterator::Throw, FunctionLength::ONE);
// %IteratorPrototype% [ @@iterator ]

View File

@ -44,8 +44,13 @@ JSTaggedValue BuiltinsIterator::Return(EcmaRuntimeCallInfo *argv)
BUILTINS_API_TRACE(argv->GetThread(), Iterator, Return);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
JSHandle<JSObject> iterResult = JSIterator::CreateIterResultObject(thread, value, true);
JSHandle<JSTaggedValue> thisValue = GetCallArg(argv, 0);
return ReturnInternal(thread, thisValue);
}
JSTaggedValue BuiltinsIterator::ReturnInternal(JSThread *thread, JSHandle<JSTaggedValue> thisValue)
{
JSHandle<JSObject> iterResult = JSIterator::CreateIterResultObject(thread, thisValue, true);
return iterResult.GetTaggedValue();
}

View File

@ -30,6 +30,8 @@ public:
static JSTaggedValue Return(EcmaRuntimeCallInfo *argv);
static JSTaggedValue ReturnInternal(JSThread *thread, JSHandle<JSTaggedValue> thisValue);
static JSTaggedValue GetIteratorObj(EcmaRuntimeCallInfo *argv);
};
} // namespace panda::ecmascript::builtins

View File

@ -129,7 +129,8 @@ namespace panda::ecmascript::kungfu {
V(MAP_ITERATOR_PROTO_NEXT) \
V(SET_ITERATOR_PROTO_NEXT) \
V(STRING_ITERATOR_PROTO_NEXT) \
V(ARRAY_ITERATOR_PROTO_NEXT)
V(ARRAY_ITERATOR_PROTO_NEXT) \
V(ITERATOR_PROTO_RETURN)
class BuiltinsStubCSigns {
public:
@ -144,7 +145,7 @@ public:
#undef DEF_STUB_ID
BUILTINS_CONSTRUCTOR_STUB_FIRST = BooleanConstructor,
TYPED_BUILTINS_FIRST = SQRT,
TYPED_BUILTINS_LAST = ARRAY_ITERATOR_PROTO_NEXT,
TYPED_BUILTINS_LAST = ITERATOR_PROTO_RETURN,
TYPED_BUILTINS_MATH_FIRST = SQRT,
TYPED_BUILTINS_MATH_LAST = FLOOR,
INVALID = 0xFF,
@ -213,6 +214,7 @@ public:
case BuiltinsStubCSigns::ID::SET_ITERATOR_PROTO_NEXT:
case BuiltinsStubCSigns::ID::STRING_ITERATOR_PROTO_NEXT:
case BuiltinsStubCSigns::ID::ARRAY_ITERATOR_PROTO_NEXT:
case BuiltinsStubCSigns::ID::ITERATOR_PROTO_RETURN:
return true;
default:
return false;
@ -260,6 +262,8 @@ public:
return ConstantIndex::STRING_ITERATOR_PROTO_NEXT_INDEX;
case BuiltinsStubCSigns::ID::ARRAY_ITERATOR_PROTO_NEXT:
return ConstantIndex::ARRAY_ITERATOR_PROTO_NEXT_INDEX;
case BuiltinsStubCSigns::ID::ITERATOR_PROTO_RETURN:
return ConstantIndex::ITERATOR_PROTO_RETURN_INDEX;
case BuiltinsStubCSigns::ID::StringFromCharCode:
return ConstantIndex::STRING_FROM_CHAR_CODE_INDEX;
default:

View File

@ -57,6 +57,9 @@ void BuiltinLowering::LowerTypedCallBuitin(GateRef gate)
case BUILTINS_STUB_ID(ARRAY_ITERATOR_PROTO_NEXT):
LowerIteratorNext(gate, id);
break;
case BUILTINS_STUB_ID(ITERATOR_PROTO_RETURN):
LowerIteratorReturn(gate, id);
break;
case BUILTINS_STUB_ID(NumberConstructor):
LowerNumberConstructor(gate);
break;
@ -381,6 +384,7 @@ GateRef BuiltinLowering::CheckPara(GateRef gate, GateRef funcCheck)
case BuiltinsStubCSigns::ID::SET_ITERATOR_PROTO_NEXT:
case BuiltinsStubCSigns::ID::STRING_ITERATOR_PROTO_NEXT:
case BuiltinsStubCSigns::ID::ARRAY_ITERATOR_PROTO_NEXT:
case BuiltinsStubCSigns::ID::ITERATOR_PROTO_RETURN:
case BuiltinsStubCSigns::ID::NumberConstructor:
case BuiltinsStubCSigns::ID::StringFromCharCode:
// Don't need check para
@ -462,6 +466,22 @@ void BuiltinLowering::LowerIteratorNext(GateRef gate, BuiltinsStubCSigns::ID id)
ReplaceHirWithValue(gate, result);
}
void BuiltinLowering::LowerIteratorReturn(GateRef gate, BuiltinsStubCSigns::ID id)
{
GateRef glue = acc_.GetGlueFromArgList();
GateRef thisObj = acc_.GetValueIn(gate, 0);
GateRef result = Circuit::NullGate();
switch (id) {
case BUILTINS_STUB_ID(ITERATOR_PROTO_RETURN): {
result = LowerCallRuntime(glue, gate, RTSTUB_ID(IteratorReturn), { thisObj }, true);
break;
}
default:
UNREACHABLE();
}
ReplaceHirWithValue(gate, result);
}
void BuiltinLowering::LowerNumberConstructor(GateRef gate)
{
auto env = builder_.GetCurrentEnvironment();

View File

@ -48,6 +48,7 @@ private:
void LowerTypedStringify(GateRef gate);
void LowerBuiltinIterator(GateRef gate, BuiltinsStubCSigns::ID id);
void LowerIteratorNext(GateRef gate, BuiltinsStubCSigns::ID id);
void LowerIteratorReturn(GateRef gate, BuiltinsStubCSigns::ID id);
void LowerNumberConstructor(GateRef gate);
Circuit *circuit_ {nullptr};

View File

@ -451,6 +451,7 @@ public:
GateRef MigrateArrayWithKind(GateRef receiver, GateRef oldElementsKind, GateRef newElementsKind);
// **************************** Middle IR ****************************
GateRef EcmaObjectCheck(GateRef gate);
GateRef HeapObjectCheck(GateRef gate, GateRef frameState);
GateRef ProtoChangeMarkerCheck(GateRef gate, GateRef frameState = Gate::InvalidGateRef);
GateRef StableArrayCheck(GateRef gate, ElementsKind kind, ArrayMetaDataAccessor::Mode mode);
@ -628,6 +629,8 @@ public:
inline GateRef TaggedIsStoreTSHandler(GateRef x);
inline GateRef TaggedIsTransWithProtoHandler(GateRef x);
inline GateRef TaggedIsUndefinedOrNull(GateRef x);
inline GateRef TaggedIsUndefinedOrNullOrHole(GateRef x);
inline GateRef TaggedIsNotUndefinedAndNullAndHole(GateRef x);
inline GateRef TaggedIsNotUndefinedAndNull(GateRef x);
inline GateRef TaggedIsUndefinedOrHole(GateRef x);
inline GateRef TaggedIsTrue(GateRef x);

View File

@ -1066,10 +1066,11 @@ bool GateAccessor::IsConstantUndefined(GateRef gate) const
return IsConstantValue(gate, JSTaggedValue::VALUE_UNDEFINED);
}
bool GateAccessor::IsUndefinedOrNull(GateRef gate) const
bool GateAccessor::IsUndefinedOrNullOrHole(GateRef gate) const
{
return IsConstantValue(gate, JSTaggedValue::VALUE_UNDEFINED) ||
IsConstantValue(gate, JSTaggedValue::VALUE_NULL);
IsConstantValue(gate, JSTaggedValue::VALUE_NULL) ||
IsConstantValue(gate, JSTaggedValue::VALUE_HOLE);
}
bool GateAccessor::IsTypedOperator(GateRef gate) const

View File

@ -506,7 +506,7 @@ public:
bool IsDependSelector(GateRef gate) const;
bool IsConstantValue(GateRef gate, uint64_t value) const;
bool IsConstantUndefined(GateRef gate) const;
bool IsUndefinedOrNull(GateRef gate) const;
bool IsUndefinedOrNullOrHole(GateRef gate) const;
bool IsConstantNumber(GateRef gate) const;
bool IsTypedOperator(GateRef gate) const;
bool IsNotWrite(GateRef gate) const;

View File

@ -56,6 +56,19 @@ GateRef CircuitBuilder::HeapObjectCheck(GateRef gate, GateRef frameState)
return ret;
}
GateRef CircuitBuilder::EcmaObjectCheck(GateRef value)
{
auto currentLabel = env_->GetCurrentLabel();
auto currentControl = currentLabel->GetControl();
auto currentDepend = currentLabel->GetDepend();
auto frameState = acc_.FindNearestFrameState(currentDepend);
GateRef ret = GetCircuit()->NewGate(circuit_->EcmaObjectCheck(),
MachineType::I1, {currentControl, currentDepend, value, frameState}, GateType::NJSValue());
currentLabel->SetControl(ret);
currentLabel->SetDepend(ret);
return ret;
}
GateRef CircuitBuilder::ProtoChangeMarkerCheck(GateRef gate, GateRef frameState)
{
auto currentLabel = env_->GetCurrentLabel();

View File

@ -461,6 +461,11 @@ GateRef CircuitBuilder::TaggedIsUndefinedOrNull(GateRef x)
return result;
}
GateRef CircuitBuilder::TaggedIsUndefinedOrNullOrHole(GateRef x)
{
return BoolOr(TaggedIsUndefinedOrNull(x), TaggedIsHole(x));
}
GateRef CircuitBuilder::TaggedIsNotUndefinedAndNull(GateRef x)
{
x = ChangeTaggedPointerToInt64(x);
@ -471,6 +476,11 @@ GateRef CircuitBuilder::TaggedIsNotUndefinedAndNull(GateRef x)
return result;
}
GateRef CircuitBuilder::TaggedIsNotUndefinedAndNullAndHole(GateRef x)
{
return BoolAnd(TaggedIsNotUndefinedAndNull(x), TaggedIsNotHole(x));
}
GateRef CircuitBuilder::TaggedIsUndefinedOrHole(GateRef x)
{
GateRef isUndefined = TaggedIsUndefined(x);

View File

@ -40,6 +40,9 @@ GateRef MCRLowering::VisitGate(GateRef gate)
case OpCode::HEAP_OBJECT_CHECK:
LowerHeapObjectCheck(gate);
break;
case OpCode::ECMA_OBJECT_CHECK:
LowerEcmaObjectCheck(gate);
break;
case OpCode::ELEMENTSKIND_CHECK:
LowerElementskindCheck(gate);
break;
@ -198,6 +201,17 @@ void MCRLowering::LowerHeapObjectCheck(GateRef gate)
acc_.ReplaceGate(gate, builder_.GetState(), builder_.GetDepend(), Circuit::NullGate());
}
void MCRLowering::LowerEcmaObjectCheck(GateRef gate)
{
Environment env(gate, circuit_, &builder_);
GateRef frameState = acc_.GetFrameState(gate);
GateRef value = acc_.GetValueIn(gate, 0);
GateRef condition = builder_.BoolAnd(builder_.TaggedIsHeapObject(value),
builder_.TaggedObjectIsEcmaObject(value));
builder_.DeoptCheck(condition, frameState, DeoptType::NOTECMAOBJECT1);
acc_.ReplaceGate(gate, builder_.GetState(), builder_.GetDepend(), Circuit::NullGate());
}
void MCRLowering::LowerTaggedIsHeapObject(GateRef gate)
{
Environment env(gate, circuit_, &builder_);

View File

@ -38,6 +38,7 @@ private:
void DeleteStateSplit(GateRef gate);
void LowerArrayGuardianCheck(GateRef gate);
void LowerEcmaObjectCheck(GateRef gate);
void LowerHeapObjectCheck(GateRef gate);
void LowerTaggedIsHeapObject(GateRef gate);
void LowerIsMarkerCellValid(GateRef gate);

View File

@ -41,6 +41,7 @@ namespace panda::ecmascript::kungfu {
V(FinishAllocate, FINISH_ALLOCATE, GateFlags::NONE_FLAG, 0, 1, 1) \
V(FlattenTreeStringCheck, FLATTEN_TREE_STRING_CHECK, GateFlags::CHECKABLE, 1, 1, 1) \
V(HeapObjectCheck, HEAP_OBJECT_CHECK, GateFlags::CHECKABLE, 1, 1, 1) \
V(EcmaObjectCheck, ECMA_OBJECT_CHECK, GateFlags::CHECKABLE, 1, 1, 1) \
V(ProtoChangeMarkerCheck, PROTO_CHANGE_MARKER_CHECK, GateFlags::CHECKABLE, 1, 1, 1) \
V(LookUpHolder, LOOK_UP_HOLDER, GateFlags::NO_WRITE, 1, 1, 3) \
V(LoadGetter, LOAD_GETTER, GateFlags::NO_WRITE, 0, 1, 2) \

View File

@ -77,6 +77,9 @@ void NTypeBytecodeLowering::Lower(GateRef gate)
case EcmaOpcode::THROW_IFSUPERNOTCORRECTCALL_PREF_IMM16:
LowerThrowIfSuperNotCorrectCall(gate);
break;
case EcmaOpcode::THROW_IFNOTOBJECT_PREF_V8:
LowerThrowIfNotObject(gate);
break;
case EcmaOpcode::LDLEXVAR_IMM4_IMM4:
case EcmaOpcode::LDLEXVAR_IMM8_IMM8:
case EcmaOpcode::WIDE_LDLEXVAR_PREF_IMM16_IMM16:
@ -128,6 +131,14 @@ void NTypeBytecodeLowering::LowerThrowIfSuperNotCorrectCall(GateRef gate)
acc_.ReplaceHirAndDeleteIfException(gate, builder_.GetStateDepend(), builder_.TaggedTrue());
}
void NTypeBytecodeLowering::LowerThrowIfNotObject(GateRef gate)
{
AddProfiling(gate);
GateRef value = acc_.GetValueIn(gate, 0); // 0: the first parameter
builder_.EcmaObjectCheck(value);
acc_.ReplaceHirAndDeleteIfException(gate, builder_.GetStateDepend(), Circuit::NullGate());
}
void NTypeBytecodeLowering::LowerLdLexVar(GateRef gate)
{
AddProfiling(gate);

View File

@ -58,6 +58,7 @@ private:
void LowerStModuleVar(GateRef gate);
void LowerNTypedStOwnByName(GateRef gate);
void LowerThrowIfSuperNotCorrectCall(GateRef gate);
void LowerThrowIfNotObject(GateRef gate);
bool IsLogEnabled() const
{

View File

@ -498,7 +498,7 @@ void NumberSpeculativeLowering::VisitUndefinedStrictEqOrUndefinedStrictNotEq(Gat
acc_.GetTypedBinaryOp(gate) == TypedBinOp::TYPED_STRICTNOTEQ);
GateRef left = acc_.GetValueIn(gate, 0);
GateRef right = acc_.GetValueIn(gate, 1);
ASSERT(acc_.IsUndefinedOrNull(left) || acc_.IsUndefinedOrNull(right));
ASSERT(acc_.IsUndefinedOrNullOrHole(left) || acc_.IsUndefinedOrNullOrHole(right));
GateRef result = Circuit::NullGate();
if (acc_.GetTypedBinaryOp(gate) == TypedBinOp::TYPED_STRICTEQ) {
result = builder_.Equal(left, right);
@ -517,13 +517,13 @@ void NumberSpeculativeLowering::VisitUndefinedEqOrUndefinedNotEq(GateRef gate)
acc_.GetTypedBinaryOp(gate) == TypedBinOp::TYPED_NOTEQ);
GateRef left = acc_.GetValueIn(gate, 0);
GateRef right = acc_.GetValueIn(gate, 1);
ASSERT(acc_.IsUndefinedOrNull(left) || acc_.IsUndefinedOrNull(right));
GateRef valueGate = acc_.IsUndefinedOrNull(left) ? right : left;
ASSERT(acc_.IsUndefinedOrNullOrHole(left) || acc_.IsUndefinedOrNullOrHole(right));
GateRef valueGate = acc_.IsUndefinedOrNullOrHole(left) ? right : left;
GateRef result = Circuit::NullGate();
if (acc_.GetTypedBinaryOp(gate) == TypedBinOp::TYPED_EQ) {
result = builder_.TaggedIsUndefinedOrNull(valueGate);
result = builder_.TaggedIsUndefinedOrNullOrHole(valueGate);
} else {
result = builder_.TaggedIsNotUndefinedAndNull(valueGate);
result = builder_.TaggedIsNotUndefinedAndNullAndHole(valueGate);
}
acc_.ReplaceGate(gate, builder_.GetState(), builder_.GetDepend(), result);
}

View File

@ -201,7 +201,7 @@ GateRef NumberSpeculativeRetype::VisitUndefinedEqualCompareOrUndefinedNotEqualCo
acc_.GetTypedBinaryOp(gate) == TypedBinOp::TYPED_NOTEQ);
GateRef left = acc_.GetValueIn(gate, 0);
GateRef right = acc_.GetValueIn(gate, 1);
ASSERT((acc_.IsUndefinedOrNull(left)) || (acc_.IsUndefinedOrNull(right)));
ASSERT((acc_.IsUndefinedOrNullOrHole(left)) || (acc_.IsUndefinedOrNullOrHole(right)));
if (IsRetype()) {
return SetOutputType(gate, GateType::BooleanType());
}

View File

@ -75,6 +75,7 @@ enum class TypedCallTargetCheckOp : uint8_t;
V(InconsistentHClass8, INCONSISTENTHCLASS8) \
V(InconsistentHClass9, INCONSISTENTHCLASS9) \
V(InconsistentHClass10, INCONSISTENTHCLASS10) \
V(NotEcmaObject1, NOTECMAOBJECT1) \
V(NotNewObj1, NOTNEWOBJ1) \
V(NotNewObj2, NOTNEWOBJ2) \
V(NotNewObj3, NOTNEWOBJ3) \

View File

@ -72,9 +72,9 @@ public:
bool HasStringType() const;
bool LeftOrRightIsUndefinedOrNull() const
bool LeftOrRightIsUndefinedOrNullOrHole() const
{
return acc_.IsUndefinedOrNull(left_) || acc_.IsUndefinedOrNull(right_);
return acc_.IsUndefinedOrNullOrHole(left_) || acc_.IsUndefinedOrNullOrHole(right_);
}
private:

View File

@ -383,7 +383,7 @@ void TypedBytecodeLowering::LowerTypedEqOrNotEq(GateRef gate)
PGOTypeRef pgoType = acc_.TryGetPGOType(gate);
BinOpTypeInfoAccessor tacc(thread_, circuit_, gate);
if (tacc.LeftOrRightIsUndefinedOrNull() || tacc.HasNumberType()) {
if (tacc.LeftOrRightIsUndefinedOrNullOrHole() || tacc.HasNumberType()) {
AddProfiling(gate);
GateRef result = builder_.TypedBinaryOp<Op>(
left, right, leftType, rightType, gateType, pgoType);

View File

@ -169,6 +169,7 @@ class ObjectFactory;
V(JSTaggedValue, SetIteratorProtoNext, SET_ITERATOR_PROTO_NEXT_INDEX, ecma_roots_special) \
V(JSTaggedValue, StringIteratorProtoNext, STRING_ITERATOR_PROTO_NEXT_INDEX, ecma_roots_special) \
V(JSTaggedValue, ArrayIteratorProtoNext, ARRAY_ITERATOR_PROTO_NEXT_INDEX, ecma_roots_special) \
V(JSTaggedValue, IteratorProtoReturn, ITERATOR_PROTO_RETURN_INDEX, ecma_roots_special) \
V(JSTaggedValue, StringFromCharCode, STRING_FROM_CHAR_CODE_INDEX, ecma_roots_special)
// All of type JSTaggedValue

View File

@ -27,6 +27,7 @@
#include "ecmascript/js_stable_array.h"
#include "ecmascript/js_tagged_value.h"
#include "ecmascript/base/typed_array_helper.h"
#include "ecmascript/builtins/builtins_iterator.h"
#include "ecmascript/builtins/builtins_string_iterator.h"
#include "ecmascript/compiler/builtins/containers_stub_builder.h"
#include "ecmascript/builtins/builtins_array.h"
@ -963,6 +964,13 @@ DEF_RUNTIME_STUBS(ArrayIteratorNext)
return JSArrayIterator::NextInternal(thread, thisObj).GetRawData();
}
DEF_RUNTIME_STUBS(IteratorReturn)
{
RUNTIME_STUBS_HEADER(IteratorReturn);
JSHandle<JSTaggedValue> thisObj = GetHArg<JSTaggedValue>(argv, argc, 0); // 0: means the zeroth parameter
return builtins::BuiltinsIterator::ReturnInternal(thread, thisObj).GetRawData();
}
DEF_RUNTIME_STUBS(GetNextPropName)
{
RUNTIME_STUBS_HEADER(GetNextPropName);

View File

@ -157,9 +157,9 @@ using FastCallAotEntryType = JSTaggedValue (*)(uintptr_t glue, uint32_t argc, co
V(GetTaggedArrayPtrTest) \
V(NewInternalString) \
V(NewTaggedArray) \
V(NewCOWTaggedArray) \
V(NewMutantTaggedArray) \
V(NewCOWMutantTaggedArray) \
V(NewCOWTaggedArray) \
V(NewMutantTaggedArray) \
V(NewCOWMutantTaggedArray) \
V(CopyArray) \
V(NumberToString) \
V(IntToString) \
@ -176,9 +176,9 @@ using FastCallAotEntryType = JSTaggedValue (*)(uintptr_t glue, uint32_t argc, co
V(UpdateLayOutAndAddTransition) \
V(CopyAndUpdateObjLayout) \
V(UpdateHClassForElementsKind) \
V(IsElementsKindSwitchOn) \
V(IsElementsKindSwitchOn) \
V(SetValueWithElementsKind) \
V(UpdateArrayHClassAndMigrateArrayWithKind) \
V(UpdateArrayHClassAndMigrateArrayWithKind) \
V(MigrateArrayWithKind) \
V(GetTaggedValueWithElementsKind) \
V(TryRestoreElementsKind) \
@ -215,6 +215,7 @@ using FastCallAotEntryType = JSTaggedValue (*)(uintptr_t glue, uint32_t argc, co
V(SetIteratorNext) \
V(StringIteratorNext) \
V(ArrayIteratorNext) \
V(IteratorReturn) \
V(GetNextPropName) \
V(GetNextPropNameSlowpath) \
V(ThrowIfNotObject) \

View File

@ -19,4 +19,32 @@ function foo({x = 11, y = 22})
return x + y;
}
assert_equal(foo({}), 33);
assert_equal(foo({}), 33);
// normal
let [] = [];
let [c, d] = [1, 2];
let [e, , ,...f] = [1, 2, 3, 4, 5, 6];
assert_equal(c, 1); // 1
assert_equal(d, 2); // 2
assert_equal(e, 1); // 1
assert_equal(f, [4,5,6]); // 4,5,6
// destructuring more elements
const foo1 = ["one", "two"];
const [red, yellow, green, blue] = foo1;
assert_equal(red, "one"); // "one"
assert_equal(yellow, "two"); // "two"
assert_equal(green, undefined); // undefined
assert_equal(blue, undefined); //undefined
// swap
let a = 1;
let b = 3;
[a, b] = [b, a];
assert_equal(a, 3); // 3
assert_equal(b, 1); // 1
const arr = [1, 2, 3];
[arr[2], arr[1]] = [arr[1], arr[2]];
assert_equal(arr, [1, 3, 2]); // [1, 3, 2]