14934f76bSHeejin Ahn //=== WebAssemblyLateEHPrepare.cpp - WebAssembly Exception Preparation -===//
24934f76bSHeejin Ahn //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
64934f76bSHeejin Ahn //
74934f76bSHeejin Ahn //===----------------------------------------------------------------------===//
84934f76bSHeejin Ahn ///
94934f76bSHeejin Ahn /// \file
104934f76bSHeejin Ahn /// \brief Does various transformations for exception handling.
114934f76bSHeejin Ahn ///
124934f76bSHeejin Ahn //===----------------------------------------------------------------------===//
134934f76bSHeejin Ahn
144934f76bSHeejin Ahn #include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
150b2bc69bSHeejin Ahn #include "Utils/WebAssemblyUtilities.h"
164934f76bSHeejin Ahn #include "WebAssembly.h"
174934f76bSHeejin Ahn #include "WebAssemblySubtarget.h"
184e4df1e3SHeejin Ahn #include "llvm/ADT/SmallPtrSet.h"
19989f1c72Sserge-sans-paille #include "llvm/CodeGen/MachineFunctionPass.h"
204934f76bSHeejin Ahn #include "llvm/CodeGen/MachineInstrBuilder.h"
214934f76bSHeejin Ahn #include "llvm/CodeGen/WasmEHFuncInfo.h"
224934f76bSHeejin Ahn #include "llvm/MC/MCAsmInfo.h"
23904cd3e0SReid Kleckner #include "llvm/Support/Debug.h"
24fe0006c8SSimon Pilgrim #include "llvm/Target/TargetMachine.h"
254934f76bSHeejin Ahn using namespace llvm;
264934f76bSHeejin Ahn
278b49b6beSHeejin Ahn #define DEBUG_TYPE "wasm-late-eh-prepare"
284934f76bSHeejin Ahn
294934f76bSHeejin Ahn namespace {
304934f76bSHeejin Ahn class WebAssemblyLateEHPrepare final : public MachineFunctionPass {
getPassName() const314934f76bSHeejin Ahn StringRef getPassName() const override {
32d6f48786SHeejin Ahn return "WebAssembly Late Prepare Exception";
334934f76bSHeejin Ahn }
344934f76bSHeejin Ahn
354934f76bSHeejin Ahn bool runOnMachineFunction(MachineFunction &MF) override;
364e4df1e3SHeejin Ahn bool removeUnreachableEHPads(MachineFunction &MF);
374cd3f4b3SHeejin Ahn void recordCatchRetBBs(MachineFunction &MF);
389e4eadebSHeejin Ahn bool hoistCatches(MachineFunction &MF);
399e4eadebSHeejin Ahn bool addCatchAlls(MachineFunction &MF);
40b47a18cdSHeejin Ahn bool replaceFuncletReturns(MachineFunction &MF);
41b47a18cdSHeejin Ahn bool removeUnnecessaryUnreachables(MachineFunction &MF);
420bb98650SHeejin Ahn bool restoreStackPointer(MachineFunction &MF);
434934f76bSHeejin Ahn
444cd3f4b3SHeejin Ahn MachineBasicBlock *getMatchingEHPad(MachineInstr *MI);
454e4df1e3SHeejin Ahn SmallPtrSet<MachineBasicBlock *, 8> CatchRetBBs;
464cd3f4b3SHeejin Ahn
474934f76bSHeejin Ahn public:
484934f76bSHeejin Ahn static char ID; // Pass identification, replacement for typeid
WebAssemblyLateEHPrepare()494934f76bSHeejin Ahn WebAssemblyLateEHPrepare() : MachineFunctionPass(ID) {}
504934f76bSHeejin Ahn };
514934f76bSHeejin Ahn } // end anonymous namespace
524934f76bSHeejin Ahn
534934f76bSHeejin Ahn char WebAssemblyLateEHPrepare::ID = 0;
544934f76bSHeejin Ahn INITIALIZE_PASS(WebAssemblyLateEHPrepare, DEBUG_TYPE,
55a93e7261SHeejin Ahn "WebAssembly Late Exception Preparation", false, false)
564934f76bSHeejin Ahn
createWebAssemblyLateEHPrepare()574934f76bSHeejin Ahn FunctionPass *llvm::createWebAssemblyLateEHPrepare() {
584934f76bSHeejin Ahn return new WebAssemblyLateEHPrepare();
594934f76bSHeejin Ahn }
604934f76bSHeejin Ahn
614934f76bSHeejin Ahn // Returns the nearest EH pad that dominates this instruction. This does not use
624934f76bSHeejin Ahn // dominator analysis; it just does BFS on its predecessors until arriving at an
634934f76bSHeejin Ahn // EH pad. This assumes valid EH scopes so the first EH pad it arrives in all
644934f76bSHeejin Ahn // possible search paths should be the same.
654934f76bSHeejin Ahn // Returns nullptr in case it does not find any EH pad in the search, or finds
664934f76bSHeejin Ahn // multiple different EH pads.
674cd3f4b3SHeejin Ahn MachineBasicBlock *
getMatchingEHPad(MachineInstr * MI)684cd3f4b3SHeejin Ahn WebAssemblyLateEHPrepare::getMatchingEHPad(MachineInstr *MI) {
694934f76bSHeejin Ahn MachineFunction *MF = MI->getParent()->getParent();
704934f76bSHeejin Ahn SmallVector<MachineBasicBlock *, 2> WL;
714934f76bSHeejin Ahn SmallPtrSet<MachineBasicBlock *, 2> Visited;
724934f76bSHeejin Ahn WL.push_back(MI->getParent());
734934f76bSHeejin Ahn MachineBasicBlock *EHPad = nullptr;
744934f76bSHeejin Ahn while (!WL.empty()) {
754934f76bSHeejin Ahn MachineBasicBlock *MBB = WL.pop_back_val();
76*b254d671SKazu Hirata if (!Visited.insert(MBB).second)
774934f76bSHeejin Ahn continue;
784934f76bSHeejin Ahn if (MBB->isEHPad()) {
794934f76bSHeejin Ahn if (EHPad && EHPad != MBB)
804934f76bSHeejin Ahn return nullptr;
814934f76bSHeejin Ahn EHPad = MBB;
824934f76bSHeejin Ahn continue;
834934f76bSHeejin Ahn }
844934f76bSHeejin Ahn if (MBB == &MF->front())
854934f76bSHeejin Ahn return nullptr;
864cd3f4b3SHeejin Ahn for (auto *Pred : MBB->predecessors())
874cd3f4b3SHeejin Ahn if (!CatchRetBBs.count(Pred)) // We don't go into child scopes
884cd3f4b3SHeejin Ahn WL.push_back(Pred);
894934f76bSHeejin Ahn }
904934f76bSHeejin Ahn return EHPad;
914934f76bSHeejin Ahn }
924934f76bSHeejin Ahn
93095796a3SHeejin Ahn // Erase the specified BBs if the BB does not have any remaining predecessors,
94095796a3SHeejin Ahn // and also all its dead children.
9583996e4dSBenjamin Kramer template <typename Container>
eraseDeadBBsAndChildren(const Container & MBBs)96095796a3SHeejin Ahn static void eraseDeadBBsAndChildren(const Container &MBBs) {
9783996e4dSBenjamin Kramer SmallVector<MachineBasicBlock *, 8> WL(MBBs.begin(), MBBs.end());
984e4df1e3SHeejin Ahn SmallPtrSet<MachineBasicBlock *, 8> Deleted;
994934f76bSHeejin Ahn while (!WL.empty()) {
1004934f76bSHeejin Ahn MachineBasicBlock *MBB = WL.pop_back_val();
1014e4df1e3SHeejin Ahn if (Deleted.count(MBB) || !MBB->pred_empty())
102095796a3SHeejin Ahn continue;
1030e219b64SKazu Hirata SmallVector<MachineBasicBlock *, 4> Succs(MBB->successors());
104b68d5914SHeejin Ahn WL.append(MBB->succ_begin(), MBB->succ_end());
105b68d5914SHeejin Ahn for (auto *Succ : Succs)
1064934f76bSHeejin Ahn MBB->removeSuccessor(Succ);
1074e4df1e3SHeejin Ahn // To prevent deleting the same BB multiple times, which can happen when
1084e4df1e3SHeejin Ahn // 'MBBs' contain both a parent and a child
1094e4df1e3SHeejin Ahn Deleted.insert(MBB);
1104934f76bSHeejin Ahn MBB->eraseFromParent();
1114934f76bSHeejin Ahn }
1124934f76bSHeejin Ahn }
1134934f76bSHeejin Ahn
runOnMachineFunction(MachineFunction & MF)1144934f76bSHeejin Ahn bool WebAssemblyLateEHPrepare::runOnMachineFunction(MachineFunction &MF) {
115569f0909SHeejin Ahn LLVM_DEBUG(dbgs() << "********** Late EH Prepare **********\n"
116569f0909SHeejin Ahn "********** Function: "
117569f0909SHeejin Ahn << MF.getName() << '\n');
118569f0909SHeejin Ahn
1194934f76bSHeejin Ahn if (MF.getTarget().getMCAsmInfo()->getExceptionHandlingType() !=
1204934f76bSHeejin Ahn ExceptionHandling::Wasm)
1214934f76bSHeejin Ahn return false;
1224934f76bSHeejin Ahn
1234934f76bSHeejin Ahn bool Changed = false;
124b47a18cdSHeejin Ahn if (MF.getFunction().hasPersonalityFn()) {
1254e4df1e3SHeejin Ahn Changed |= removeUnreachableEHPads(MF);
1264cd3f4b3SHeejin Ahn recordCatchRetBBs(MF);
1279e4eadebSHeejin Ahn Changed |= hoistCatches(MF);
1289e4eadebSHeejin Ahn Changed |= addCatchAlls(MF);
129b47a18cdSHeejin Ahn Changed |= replaceFuncletReturns(MF);
130b47a18cdSHeejin Ahn }
131b47a18cdSHeejin Ahn Changed |= removeUnnecessaryUnreachables(MF);
1329e4eadebSHeejin Ahn if (MF.getFunction().hasPersonalityFn())
1330bb98650SHeejin Ahn Changed |= restoreStackPointer(MF);
1344934f76bSHeejin Ahn return Changed;
1354934f76bSHeejin Ahn }
1364934f76bSHeejin Ahn
1374e4df1e3SHeejin Ahn // Remove unreachable EH pads and its children. If they remain, CFG
1384e4df1e3SHeejin Ahn // stackification can be tricky.
removeUnreachableEHPads(MachineFunction & MF)1394e4df1e3SHeejin Ahn bool WebAssemblyLateEHPrepare::removeUnreachableEHPads(MachineFunction &MF) {
1404e4df1e3SHeejin Ahn SmallVector<MachineBasicBlock *, 4> ToDelete;
1414e4df1e3SHeejin Ahn for (auto &MBB : MF)
1424e4df1e3SHeejin Ahn if (MBB.isEHPad() && MBB.pred_empty())
1434e4df1e3SHeejin Ahn ToDelete.push_back(&MBB);
1444e4df1e3SHeejin Ahn eraseDeadBBsAndChildren(ToDelete);
1454e4df1e3SHeejin Ahn return !ToDelete.empty();
1464e4df1e3SHeejin Ahn }
1474e4df1e3SHeejin Ahn
1484e4df1e3SHeejin Ahn // Record which BB ends with catchret instruction, because this will be replaced
1494e4df1e3SHeejin Ahn // with 'br's later. This set of catchret BBs is necessary in 'getMatchingEHPad'
1504e4df1e3SHeejin Ahn // function.
recordCatchRetBBs(MachineFunction & MF)1514cd3f4b3SHeejin Ahn void WebAssemblyLateEHPrepare::recordCatchRetBBs(MachineFunction &MF) {
1524cd3f4b3SHeejin Ahn CatchRetBBs.clear();
1534cd3f4b3SHeejin Ahn for (auto &MBB : MF) {
1544cd3f4b3SHeejin Ahn auto Pos = MBB.getFirstTerminator();
1554cd3f4b3SHeejin Ahn if (Pos == MBB.end())
1564cd3f4b3SHeejin Ahn continue;
1574cd3f4b3SHeejin Ahn MachineInstr *TI = &*Pos;
1584cd3f4b3SHeejin Ahn if (TI->getOpcode() == WebAssembly::CATCHRET)
1594cd3f4b3SHeejin Ahn CatchRetBBs.insert(&MBB);
1604cd3f4b3SHeejin Ahn }
1614cd3f4b3SHeejin Ahn }
1624cd3f4b3SHeejin Ahn
1639e4eadebSHeejin Ahn // Hoist catch instructions to the beginning of their matching EH pad BBs in
1649e4eadebSHeejin Ahn // case,
1659e4eadebSHeejin Ahn // (1) catch instruction is not the first instruction in EH pad.
1669e4eadebSHeejin Ahn // ehpad:
1679e4eadebSHeejin Ahn // some_other_instruction
1689e4eadebSHeejin Ahn // ...
1699e4eadebSHeejin Ahn // %exn = catch 0
1709e4eadebSHeejin Ahn // (2) catch instruction is in a non-EH pad BB. For example,
1719e4eadebSHeejin Ahn // ehpad:
1729e4eadebSHeejin Ahn // br bb0
1739e4eadebSHeejin Ahn // bb0:
1749e4eadebSHeejin Ahn // %exn = catch 0
hoistCatches(MachineFunction & MF)1759e4eadebSHeejin Ahn bool WebAssemblyLateEHPrepare::hoistCatches(MachineFunction &MF) {
1769e4eadebSHeejin Ahn bool Changed = false;
1779e4eadebSHeejin Ahn SmallVector<MachineInstr *, 16> Catches;
1789e4eadebSHeejin Ahn for (auto &MBB : MF)
1799e4eadebSHeejin Ahn for (auto &MI : MBB)
1809e4eadebSHeejin Ahn if (WebAssembly::isCatch(MI.getOpcode()))
1819e4eadebSHeejin Ahn Catches.push_back(&MI);
1829e4eadebSHeejin Ahn
1839e4eadebSHeejin Ahn for (auto *Catch : Catches) {
1849e4eadebSHeejin Ahn MachineBasicBlock *EHPad = getMatchingEHPad(Catch);
1859e4eadebSHeejin Ahn assert(EHPad && "No matching EH pad for catch");
1869e4eadebSHeejin Ahn auto InsertPos = EHPad->begin();
1879e4eadebSHeejin Ahn // Skip EH_LABELs in the beginning of an EH pad if present. We don't use
1889e4eadebSHeejin Ahn // these labels at the moment, but other targets also seem to have an
1899e4eadebSHeejin Ahn // EH_LABEL instruction in the beginning of an EH pad.
1909e4eadebSHeejin Ahn while (InsertPos != EHPad->end() && InsertPos->isEHLabel())
1919e4eadebSHeejin Ahn InsertPos++;
1929e4eadebSHeejin Ahn if (InsertPos == Catch)
1939e4eadebSHeejin Ahn continue;
1949e4eadebSHeejin Ahn Changed = true;
1959e4eadebSHeejin Ahn EHPad->insert(InsertPos, Catch->removeFromParent());
1969e4eadebSHeejin Ahn }
1979e4eadebSHeejin Ahn return Changed;
1989e4eadebSHeejin Ahn }
1999e4eadebSHeejin Ahn
2009e4eadebSHeejin Ahn // Add catch_all to beginning of cleanup pads.
addCatchAlls(MachineFunction & MF)2019e4eadebSHeejin Ahn bool WebAssemblyLateEHPrepare::addCatchAlls(MachineFunction &MF) {
202095796a3SHeejin Ahn bool Changed = false;
203b47a18cdSHeejin Ahn const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
2049e4eadebSHeejin Ahn
205095796a3SHeejin Ahn for (auto &MBB : MF) {
2069e4eadebSHeejin Ahn if (!MBB.isEHPad())
2079e4eadebSHeejin Ahn continue;
208b47a18cdSHeejin Ahn auto InsertPos = MBB.begin();
2099e4eadebSHeejin Ahn // Skip EH_LABELs in the beginning of an EH pad if present.
2109e4eadebSHeejin Ahn while (InsertPos != MBB.end() && InsertPos->isEHLabel())
2119e4eadebSHeejin Ahn InsertPos++;
2129e4eadebSHeejin Ahn // This runs after hoistCatches(), so we assume that if there is a catch,
2136f2999b3SHeejin Ahn // that should be the first non-EH-label instruction in an EH pad.
2149e4eadebSHeejin Ahn if (InsertPos == MBB.end() ||
2159e4eadebSHeejin Ahn !WebAssembly::isCatch(InsertPos->getOpcode())) {
2169e4eadebSHeejin Ahn Changed = true;
217dcfec279SHeejin Ahn BuildMI(MBB, InsertPos,
218dcfec279SHeejin Ahn InsertPos == MBB.end() ? DebugLoc() : InsertPos->getDebugLoc(),
2199e4eadebSHeejin Ahn TII.get(WebAssembly::CATCH_ALL));
220095796a3SHeejin Ahn }
221095796a3SHeejin Ahn }
222095796a3SHeejin Ahn return Changed;
223095796a3SHeejin Ahn }
224095796a3SHeejin Ahn
2254e4df1e3SHeejin Ahn // Replace pseudo-instructions catchret and cleanupret with br and rethrow
2264e4df1e3SHeejin Ahn // respectively.
replaceFuncletReturns(MachineFunction & MF)2274934f76bSHeejin Ahn bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) {
2284934f76bSHeejin Ahn bool Changed = false;
2294934f76bSHeejin Ahn const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
2304934f76bSHeejin Ahn
2314934f76bSHeejin Ahn for (auto &MBB : MF) {
2324934f76bSHeejin Ahn auto Pos = MBB.getFirstTerminator();
2334934f76bSHeejin Ahn if (Pos == MBB.end())
2344934f76bSHeejin Ahn continue;
2354934f76bSHeejin Ahn MachineInstr *TI = &*Pos;
2364934f76bSHeejin Ahn
2374934f76bSHeejin Ahn switch (TI->getOpcode()) {
2384934f76bSHeejin Ahn case WebAssembly::CATCHRET: {
2394934f76bSHeejin Ahn // Replace a catchret with a branch
2404934f76bSHeejin Ahn MachineBasicBlock *TBB = TI->getOperand(0).getMBB();
2414934f76bSHeejin Ahn if (!MBB.isLayoutSuccessor(TBB))
2424934f76bSHeejin Ahn BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::BR))
2434934f76bSHeejin Ahn .addMBB(TBB);
2444934f76bSHeejin Ahn TI->eraseFromParent();
2454934f76bSHeejin Ahn Changed = true;
2464934f76bSHeejin Ahn break;
2474934f76bSHeejin Ahn }
2489e4eadebSHeejin Ahn case WebAssembly::CLEANUPRET: {
2499e4eadebSHeejin Ahn // Replace a cleanupret with a rethrow. For C++ support, currently
2509e4eadebSHeejin Ahn // rethrow's immediate argument is always 0 (= the latest exception).
25166ce4194SHeejin Ahn BuildMI(MBB, TI, TI->getDebugLoc(), TII.get(WebAssembly::RETHROW))
2529e4eadebSHeejin Ahn .addImm(0);
2534934f76bSHeejin Ahn TI->eraseFromParent();
2544934f76bSHeejin Ahn Changed = true;
2554934f76bSHeejin Ahn break;
2564934f76bSHeejin Ahn }
2574934f76bSHeejin Ahn }
2584934f76bSHeejin Ahn }
2594934f76bSHeejin Ahn return Changed;
2604934f76bSHeejin Ahn }
2614934f76bSHeejin Ahn
2624e4df1e3SHeejin Ahn // Remove unnecessary unreachables after a throw or rethrow.
removeUnnecessaryUnreachables(MachineFunction & MF)263b47a18cdSHeejin Ahn bool WebAssemblyLateEHPrepare::removeUnnecessaryUnreachables(
264b47a18cdSHeejin Ahn MachineFunction &MF) {
2654934f76bSHeejin Ahn bool Changed = false;
2664934f76bSHeejin Ahn for (auto &MBB : MF) {
267b47a18cdSHeejin Ahn for (auto &MI : MBB) {
268b47a18cdSHeejin Ahn if (MI.getOpcode() != WebAssembly::THROW &&
269b47a18cdSHeejin Ahn MI.getOpcode() != WebAssembly::RETHROW)
270b47a18cdSHeejin Ahn continue;
2714934f76bSHeejin Ahn Changed = true;
272b47a18cdSHeejin Ahn
273b47a18cdSHeejin Ahn // The instruction after the throw should be an unreachable or a branch to
274b47a18cdSHeejin Ahn // another BB that should eventually lead to an unreachable. Delete it
275b47a18cdSHeejin Ahn // because throw itself is a terminator, and also delete successors if
276b47a18cdSHeejin Ahn // any.
277b47a18cdSHeejin Ahn MBB.erase(std::next(MI.getIterator()), MBB.end());
2780e219b64SKazu Hirata SmallVector<MachineBasicBlock *, 8> Succs(MBB.successors());
279b47a18cdSHeejin Ahn for (auto *Succ : Succs)
28066ce4194SHeejin Ahn if (!Succ->isEHPad())
281b47a18cdSHeejin Ahn MBB.removeSuccessor(Succ);
282b47a18cdSHeejin Ahn eraseDeadBBsAndChildren(Succs);
2834934f76bSHeejin Ahn }
2844934f76bSHeejin Ahn }
285b47a18cdSHeejin Ahn
2864934f76bSHeejin Ahn return Changed;
2874934f76bSHeejin Ahn }
2884934f76bSHeejin Ahn
2890bb98650SHeejin Ahn // After the stack is unwound due to a thrown exception, the __stack_pointer
2900bb98650SHeejin Ahn // global can point to an invalid address. This inserts instructions that
2910bb98650SHeejin Ahn // restore __stack_pointer global.
restoreStackPointer(MachineFunction & MF)2920bb98650SHeejin Ahn bool WebAssemblyLateEHPrepare::restoreStackPointer(MachineFunction &MF) {
2930bb98650SHeejin Ahn const auto *FrameLowering = static_cast<const WebAssemblyFrameLowering *>(
2940bb98650SHeejin Ahn MF.getSubtarget().getFrameLowering());
2950bb98650SHeejin Ahn if (!FrameLowering->needsPrologForEH(MF))
2960bb98650SHeejin Ahn return false;
2970bb98650SHeejin Ahn bool Changed = false;
2980bb98650SHeejin Ahn
2990bb98650SHeejin Ahn for (auto &MBB : MF) {
3000bb98650SHeejin Ahn if (!MBB.isEHPad())
3010bb98650SHeejin Ahn continue;
3020bb98650SHeejin Ahn Changed = true;
3030bb98650SHeejin Ahn
3040bb98650SHeejin Ahn // Insert __stack_pointer restoring instructions at the beginning of each EH
3050bb98650SHeejin Ahn // pad, after the catch instruction. Here it is safe to assume that SP32
3060bb98650SHeejin Ahn // holds the latest value of __stack_pointer, because the only exception for
3070bb98650SHeejin Ahn // this case is when a function uses the red zone, but that only happens
3080bb98650SHeejin Ahn // with leaf functions, and we don't restore __stack_pointer in leaf
3090bb98650SHeejin Ahn // functions anyway.
3100bb98650SHeejin Ahn auto InsertPos = MBB.begin();
3116f2999b3SHeejin Ahn // Skip EH_LABELs in the beginning of an EH pad if present.
3126f2999b3SHeejin Ahn while (InsertPos != MBB.end() && InsertPos->isEHLabel())
3136f2999b3SHeejin Ahn InsertPos++;
3146f2999b3SHeejin Ahn assert(InsertPos != MBB.end() &&
3156f2999b3SHeejin Ahn WebAssembly::isCatch(InsertPos->getOpcode()) &&
3166f2999b3SHeejin Ahn "catch/catch_all should be present in every EH pad at this point");
3176f2999b3SHeejin Ahn ++InsertPos; // Skip the catch instruction
318b9a539c0SWouter van Oortmerssen FrameLowering->writeSPToGlobal(FrameLowering->getSPReg(MF), MF, MBB,
319b9a539c0SWouter van Oortmerssen InsertPos, MBB.begin()->getDebugLoc());
3200bb98650SHeejin Ahn }
3210bb98650SHeejin Ahn return Changed;
3220bb98650SHeejin Ahn }
323