xref: /src/contrib/llvm-project/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1dd58ef01SDimitry Andric // WebAssemblyMCInstLower.cpp - Convert WebAssembly MachineInstr to an MCInst //
2dd58ef01SDimitry Andric //
3e6d15924SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e6d15924SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5e6d15924SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6dd58ef01SDimitry Andric //
7dd58ef01SDimitry Andric //===----------------------------------------------------------------------===//
8dd58ef01SDimitry Andric ///
9dd58ef01SDimitry Andric /// \file
10eb11fae6SDimitry Andric /// This file contains code to lower WebAssembly MachineInstrs to their
11dd58ef01SDimitry Andric /// corresponding MCInst records.
12dd58ef01SDimitry Andric ///
13dd58ef01SDimitry Andric //===----------------------------------------------------------------------===//
14dd58ef01SDimitry Andric 
15dd58ef01SDimitry Andric #include "WebAssemblyMCInstLower.h"
16cfca06d7SDimitry Andric #include "TargetInfo/WebAssemblyTargetInfo.h"
17344a3780SDimitry Andric #include "Utils/WebAssemblyTypeUtilities.h"
1871d5a254SDimitry Andric #include "WebAssemblyAsmPrinter.h"
19c0981da4SDimitry Andric #include "WebAssemblyISelLowering.h"
20dd58ef01SDimitry Andric #include "WebAssemblyMachineFunctionInfo.h"
21b1c73532SDimitry Andric #include "WebAssemblyUtilities.h"
22dd58ef01SDimitry Andric #include "llvm/CodeGen/AsmPrinter.h"
23dd58ef01SDimitry Andric #include "llvm/CodeGen/MachineFunction.h"
24dd58ef01SDimitry Andric #include "llvm/IR/Constants.h"
25dd58ef01SDimitry Andric #include "llvm/MC/MCAsmInfo.h"
26dd58ef01SDimitry Andric #include "llvm/MC/MCContext.h"
27dd58ef01SDimitry Andric #include "llvm/MC/MCExpr.h"
28dd58ef01SDimitry Andric #include "llvm/MC/MCInst.h"
2971d5a254SDimitry Andric #include "llvm/MC/MCSymbolWasm.h"
30dd58ef01SDimitry Andric #include "llvm/Support/ErrorHandling.h"
31dd58ef01SDimitry Andric #include "llvm/Support/raw_ostream.h"
32c0981da4SDimitry Andric 
33dd58ef01SDimitry Andric using namespace llvm;
34dd58ef01SDimitry Andric 
35d8e91e46SDimitry Andric // This disables the removal of registers when lowering into MC, as required
36d8e91e46SDimitry Andric // by some current tests.
37e6d15924SDimitry Andric cl::opt<bool>
38d8e91e46SDimitry Andric     WasmKeepRegisters("wasm-keep-registers", cl::Hidden,
39d8e91e46SDimitry Andric                       cl::desc("WebAssembly: output stack registers in"
40d8e91e46SDimitry Andric                                " instruction output for test purposes only."),
41d8e91e46SDimitry Andric                       cl::init(false));
42d8e91e46SDimitry Andric 
43d8e91e46SDimitry Andric static void removeRegisterOperands(const MachineInstr *MI, MCInst &OutMI);
44d8e91e46SDimitry Andric 
45dd58ef01SDimitry Andric MCSymbol *
GetGlobalAddressSymbol(const MachineOperand & MO) const46dd58ef01SDimitry Andric WebAssemblyMCInstLower::GetGlobalAddressSymbol(const MachineOperand &MO) const {
4771d5a254SDimitry Andric   const GlobalValue *Global = MO.getGlobal();
48344a3780SDimitry Andric   if (!isa<Function>(Global)) {
49344a3780SDimitry Andric     auto *WasmSym = cast<MCSymbolWasm>(Printer.getSymbol(Global));
50344a3780SDimitry Andric     // If the symbol doesn't have an explicit WasmSymbolType yet and the
51344a3780SDimitry Andric     // GlobalValue is actually a WebAssembly global, then ensure the symbol is a
52344a3780SDimitry Andric     // WASM_SYMBOL_TYPE_GLOBAL.
53344a3780SDimitry Andric     if (WebAssembly::isWasmVarAddressSpace(Global->getAddressSpace()) &&
54344a3780SDimitry Andric         !WasmSym->getType()) {
55344a3780SDimitry Andric       const MachineFunction &MF = *MO.getParent()->getParent()->getParent();
56344a3780SDimitry Andric       const TargetMachine &TM = MF.getTarget();
57344a3780SDimitry Andric       const Function &CurrentFunc = MF.getFunction();
58c0981da4SDimitry Andric       Type *GlobalVT = Global->getValueType();
59344a3780SDimitry Andric       SmallVector<MVT, 1> VTs;
60c0981da4SDimitry Andric       computeLegalValueVTs(CurrentFunc, TM, GlobalVT, VTs);
61c0981da4SDimitry Andric 
62ecbca9f5SDimitry Andric       WebAssembly::wasmSymbolSetType(WasmSym, GlobalVT, VTs);
6377fc4c14SDimitry Andric     }
64344a3780SDimitry Andric     return WasmSym;
65344a3780SDimitry Andric   }
6671d5a254SDimitry Andric 
67b60736ecSDimitry Andric   const auto *FuncTy = cast<FunctionType>(Global->getValueType());
6871d5a254SDimitry Andric   const MachineFunction &MF = *MO.getParent()->getParent()->getParent();
6971d5a254SDimitry Andric   const TargetMachine &TM = MF.getTarget();
70044eb2f6SDimitry Andric   const Function &CurrentFunc = MF.getFunction();
7171d5a254SDimitry Andric 
72d8e91e46SDimitry Andric   SmallVector<MVT, 1> ResultMVTs;
7371d5a254SDimitry Andric   SmallVector<MVT, 4> ParamMVTs;
74cfca06d7SDimitry Andric   const auto *const F = dyn_cast<Function>(Global);
75cfca06d7SDimitry Andric   computeSignatureVTs(FuncTy, F, CurrentFunc, TM, ParamMVTs, ResultMVTs);
76ac9a064cSDimitry Andric   auto Signature = signatureFromMVTs(Ctx, ResultMVTs, ParamMVTs);
77b60736ecSDimitry Andric 
78b60736ecSDimitry Andric   bool InvokeDetected = false;
79b60736ecSDimitry Andric   auto *WasmSym = Printer.getMCSymbolForFunction(
8077fc4c14SDimitry Andric       F, WebAssembly::WasmEnableEmEH || WebAssembly::WasmEnableEmSjLj,
81ac9a064cSDimitry Andric       Signature, InvokeDetected);
82ac9a064cSDimitry Andric   WasmSym->setSignature(Signature);
83eb11fae6SDimitry Andric   WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
8471d5a254SDimitry Andric   return WasmSym;
85dd58ef01SDimitry Andric }
86dd58ef01SDimitry Andric 
GetExternalSymbolSymbol(const MachineOperand & MO) const87dd58ef01SDimitry Andric MCSymbol *WebAssemblyMCInstLower::GetExternalSymbolSymbol(
88dd58ef01SDimitry Andric     const MachineOperand &MO) const {
89344a3780SDimitry Andric   return Printer.getOrCreateWasmSymbol(MO.getSymbolName());
90dd58ef01SDimitry Andric }
91dd58ef01SDimitry Andric 
lowerSymbolOperand(const MachineOperand & MO,MCSymbol * Sym) const92e6d15924SDimitry Andric MCOperand WebAssemblyMCInstLower::lowerSymbolOperand(const MachineOperand &MO,
93e6d15924SDimitry Andric                                                      MCSymbol *Sym) const {
94e6d15924SDimitry Andric   MCSymbolRefExpr::VariantKind Kind = MCSymbolRefExpr::VK_None;
95e6d15924SDimitry Andric   unsigned TargetFlags = MO.getTargetFlags();
9671d5a254SDimitry Andric 
97e6d15924SDimitry Andric   switch (TargetFlags) {
98e6d15924SDimitry Andric     case WebAssemblyII::MO_NO_FLAG:
99e6d15924SDimitry Andric       break;
100c0981da4SDimitry Andric     case WebAssemblyII::MO_GOT_TLS:
101c0981da4SDimitry Andric       Kind = MCSymbolRefExpr::VK_WASM_GOT_TLS;
102c0981da4SDimitry Andric       break;
103e6d15924SDimitry Andric     case WebAssemblyII::MO_GOT:
104e6d15924SDimitry Andric       Kind = MCSymbolRefExpr::VK_GOT;
105e6d15924SDimitry Andric       break;
106e6d15924SDimitry Andric     case WebAssemblyII::MO_MEMORY_BASE_REL:
107e6d15924SDimitry Andric       Kind = MCSymbolRefExpr::VK_WASM_MBREL;
108e6d15924SDimitry Andric       break;
109b60736ecSDimitry Andric     case WebAssemblyII::MO_TLS_BASE_REL:
110b60736ecSDimitry Andric       Kind = MCSymbolRefExpr::VK_WASM_TLSREL;
111b60736ecSDimitry Andric       break;
112e6d15924SDimitry Andric     case WebAssemblyII::MO_TABLE_BASE_REL:
113e6d15924SDimitry Andric       Kind = MCSymbolRefExpr::VK_WASM_TBREL;
114e6d15924SDimitry Andric       break;
115e6d15924SDimitry Andric     default:
116e6d15924SDimitry Andric       llvm_unreachable("Unknown target flag on GV operand");
117e6d15924SDimitry Andric   }
118dd58ef01SDimitry Andric 
119e6d15924SDimitry Andric   const MCExpr *Expr = MCSymbolRefExpr::create(Sym, Kind, Ctx);
120e6d15924SDimitry Andric 
121e6d15924SDimitry Andric   if (MO.getOffset() != 0) {
122e6d15924SDimitry Andric     const auto *WasmSym = cast<MCSymbolWasm>(Sym);
123e6d15924SDimitry Andric     if (TargetFlags == WebAssemblyII::MO_GOT)
124e6d15924SDimitry Andric       report_fatal_error("GOT symbol references do not support offsets");
125e6d15924SDimitry Andric     if (WasmSym->isFunction())
126050e163aSDimitry Andric       report_fatal_error("Function addresses with offsets not supported");
127e6d15924SDimitry Andric     if (WasmSym->isGlobal())
128d8e91e46SDimitry Andric       report_fatal_error("Global indexes with offsets not supported");
129344a3780SDimitry Andric     if (WasmSym->isTag())
130344a3780SDimitry Andric       report_fatal_error("Tag indexes with offsets not supported");
131344a3780SDimitry Andric     if (WasmSym->isTable())
132344a3780SDimitry Andric       report_fatal_error("Table indexes with offsets not supported");
133e6d15924SDimitry Andric 
134e6d15924SDimitry Andric     Expr = MCBinaryExpr::createAdd(
135e6d15924SDimitry Andric         Expr, MCConstantExpr::create(MO.getOffset(), Ctx), Ctx);
136dd58ef01SDimitry Andric   }
137dd58ef01SDimitry Andric 
138dd58ef01SDimitry Andric   return MCOperand::createExpr(Expr);
139dd58ef01SDimitry Andric }
140dd58ef01SDimitry Andric 
lowerTypeIndexOperand(SmallVectorImpl<wasm::ValType> && Returns,SmallVectorImpl<wasm::ValType> && Params) const1411d5ae102SDimitry Andric MCOperand WebAssemblyMCInstLower::lowerTypeIndexOperand(
1427fa27ce4SDimitry Andric     SmallVectorImpl<wasm::ValType> &&Returns,
1437fa27ce4SDimitry Andric     SmallVectorImpl<wasm::ValType> &&Params) const {
144ac9a064cSDimitry Andric   auto Signature = Ctx.createWasmSignature();
145ac9a064cSDimitry Andric   Signature->Returns = std::move(Returns);
146ac9a064cSDimitry Andric   Signature->Params = std::move(Params);
1471d5ae102SDimitry Andric   MCSymbol *Sym = Printer.createTempSymbol("typeindex");
1481d5ae102SDimitry Andric   auto *WasmSym = cast<MCSymbolWasm>(Sym);
149ac9a064cSDimitry Andric   WasmSym->setSignature(Signature);
1501d5ae102SDimitry Andric   WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
1511d5ae102SDimitry Andric   const MCExpr *Expr =
1521d5ae102SDimitry Andric       MCSymbolRefExpr::create(WasmSym, MCSymbolRefExpr::VK_WASM_TYPEINDEX, Ctx);
1531d5ae102SDimitry Andric   return MCOperand::createExpr(Expr);
1541d5ae102SDimitry Andric }
1551d5ae102SDimitry Andric 
getFunctionReturns(const MachineInstr * MI,SmallVectorImpl<wasm::ValType> & Returns)1561d5ae102SDimitry Andric static void getFunctionReturns(const MachineInstr *MI,
1571d5ae102SDimitry Andric                                SmallVectorImpl<wasm::ValType> &Returns) {
1581d5ae102SDimitry Andric   const Function &F = MI->getMF()->getFunction();
1591d5ae102SDimitry Andric   const TargetMachine &TM = MI->getMF()->getTarget();
1601d5ae102SDimitry Andric   Type *RetTy = F.getReturnType();
1611d5ae102SDimitry Andric   SmallVector<MVT, 4> CallerRetTys;
1621d5ae102SDimitry Andric   computeLegalValueVTs(F, TM, RetTy, CallerRetTys);
1631d5ae102SDimitry Andric   valTypesFromMVTs(CallerRetTys, Returns);
1641d5ae102SDimitry Andric }
1651d5ae102SDimitry Andric 
lower(const MachineInstr * MI,MCInst & OutMI) const166e6d15924SDimitry Andric void WebAssemblyMCInstLower::lower(const MachineInstr *MI,
167dd58ef01SDimitry Andric                                    MCInst &OutMI) const {
168dd58ef01SDimitry Andric   OutMI.setOpcode(MI->getOpcode());
169dd58ef01SDimitry Andric 
17071d5a254SDimitry Andric   const MCInstrDesc &Desc = MI->getDesc();
171cfca06d7SDimitry Andric   unsigned NumVariadicDefs = MI->getNumExplicitDefs() - Desc.getNumDefs();
172e6d15924SDimitry Andric   for (unsigned I = 0, E = MI->getNumOperands(); I != E; ++I) {
173e6d15924SDimitry Andric     const MachineOperand &MO = MI->getOperand(I);
174dd58ef01SDimitry Andric 
175dd58ef01SDimitry Andric     MCOperand MCOp;
176dd58ef01SDimitry Andric     switch (MO.getType()) {
177dd58ef01SDimitry Andric     default:
17871d5a254SDimitry Andric       MI->print(errs());
179dd58ef01SDimitry Andric       llvm_unreachable("unknown operand type");
180050e163aSDimitry Andric     case MachineOperand::MO_MachineBasicBlock:
18171d5a254SDimitry Andric       MI->print(errs());
182050e163aSDimitry Andric       llvm_unreachable("MachineBasicBlock operand should have been rewritten");
183dd58ef01SDimitry Andric     case MachineOperand::MO_Register: {
184dd58ef01SDimitry Andric       // Ignore all implicit register operands.
185dd58ef01SDimitry Andric       if (MO.isImplicit())
186dd58ef01SDimitry Andric         continue;
187dd58ef01SDimitry Andric       const WebAssemblyFunctionInfo &MFI =
188dd58ef01SDimitry Andric           *MI->getParent()->getParent()->getInfo<WebAssemblyFunctionInfo>();
189dd58ef01SDimitry Andric       unsigned WAReg = MFI.getWAReg(MO.getReg());
190dd58ef01SDimitry Andric       MCOp = MCOperand::createReg(WAReg);
191dd58ef01SDimitry Andric       break;
192dd58ef01SDimitry Andric     }
193cfca06d7SDimitry Andric     case MachineOperand::MO_Immediate: {
194cfca06d7SDimitry Andric       unsigned DescIndex = I - NumVariadicDefs;
195cfca06d7SDimitry Andric       if (DescIndex < Desc.NumOperands) {
196e3b55780SDimitry Andric         const MCOperandInfo &Info = Desc.operands()[DescIndex];
19771d5a254SDimitry Andric         if (Info.OperandType == WebAssembly::OPERAND_TYPEINDEX) {
19871d5a254SDimitry Andric           SmallVector<wasm::ValType, 4> Returns;
19971d5a254SDimitry Andric           SmallVector<wasm::ValType, 4> Params;
20071d5a254SDimitry Andric 
20171d5a254SDimitry Andric           const MachineRegisterInfo &MRI =
20271d5a254SDimitry Andric               MI->getParent()->getParent()->getRegInfo();
20371d5a254SDimitry Andric           for (const MachineOperand &MO : MI->defs())
204b1c73532SDimitry Andric             Returns.push_back(WebAssembly::regClassToValType(
205b1c73532SDimitry Andric                 MRI.getRegClass(MO.getReg())->getID()));
20671d5a254SDimitry Andric           for (const MachineOperand &MO : MI->explicit_uses())
20771d5a254SDimitry Andric             if (MO.isReg())
208b1c73532SDimitry Andric               Params.push_back(WebAssembly::regClassToValType(
209b1c73532SDimitry Andric                   MRI.getRegClass(MO.getReg())->getID()));
21071d5a254SDimitry Andric 
21171d5a254SDimitry Andric           // call_indirect instructions have a callee operand at the end which
21271d5a254SDimitry Andric           // doesn't count as a param.
213e6d15924SDimitry Andric           if (WebAssembly::isCallIndirect(MI->getOpcode()))
21471d5a254SDimitry Andric             Params.pop_back();
21571d5a254SDimitry Andric 
2161d5ae102SDimitry Andric           // return_call_indirect instructions have the return type of the
2171d5ae102SDimitry Andric           // caller
2181d5ae102SDimitry Andric           if (MI->getOpcode() == WebAssembly::RET_CALL_INDIRECT)
2191d5ae102SDimitry Andric             getFunctionReturns(MI, Returns);
22071d5a254SDimitry Andric 
2211d5ae102SDimitry Andric           MCOp = lowerTypeIndexOperand(std::move(Returns), std::move(Params));
22271d5a254SDimitry Andric           break;
2231d5ae102SDimitry Andric         } else if (Info.OperandType == WebAssembly::OPERAND_SIGNATURE) {
2241d5ae102SDimitry Andric           auto BT = static_cast<WebAssembly::BlockType>(MO.getImm());
2251d5ae102SDimitry Andric           assert(BT != WebAssembly::BlockType::Invalid);
2261d5ae102SDimitry Andric           if (BT == WebAssembly::BlockType::Multivalue) {
2271d5ae102SDimitry Andric             SmallVector<wasm::ValType, 1> Returns;
2281d5ae102SDimitry Andric             getFunctionReturns(MI, Returns);
2291d5ae102SDimitry Andric             MCOp = lowerTypeIndexOperand(std::move(Returns),
2301d5ae102SDimitry Andric                                          SmallVector<wasm::ValType, 4>());
2311d5ae102SDimitry Andric             break;
2321d5ae102SDimitry Andric           }
23371d5a254SDimitry Andric         }
23471d5a254SDimitry Andric       }
235dd58ef01SDimitry Andric       MCOp = MCOperand::createImm(MO.getImm());
236dd58ef01SDimitry Andric       break;
237cfca06d7SDimitry Andric     }
238dd58ef01SDimitry Andric     case MachineOperand::MO_FPImmediate: {
239dd58ef01SDimitry Andric       const ConstantFP *Imm = MO.getFPImm();
240344a3780SDimitry Andric       const uint64_t BitPattern =
241344a3780SDimitry Andric           Imm->getValueAPF().bitcastToAPInt().getZExtValue();
242dd58ef01SDimitry Andric       if (Imm->getType()->isFloatTy())
243344a3780SDimitry Andric         MCOp = MCOperand::createSFPImm(static_cast<uint32_t>(BitPattern));
244dd58ef01SDimitry Andric       else if (Imm->getType()->isDoubleTy())
245344a3780SDimitry Andric         MCOp = MCOperand::createDFPImm(BitPattern);
246dd58ef01SDimitry Andric       else
247dd58ef01SDimitry Andric         llvm_unreachable("unknown floating point immediate type");
248dd58ef01SDimitry Andric       break;
249dd58ef01SDimitry Andric     }
250dd58ef01SDimitry Andric     case MachineOperand::MO_GlobalAddress:
251e6d15924SDimitry Andric       MCOp = lowerSymbolOperand(MO, GetGlobalAddressSymbol(MO));
252dd58ef01SDimitry Andric       break;
253dd58ef01SDimitry Andric     case MachineOperand::MO_ExternalSymbol:
254e6d15924SDimitry Andric       MCOp = lowerSymbolOperand(MO, GetExternalSymbolSymbol(MO));
255d8e91e46SDimitry Andric       break;
256d8e91e46SDimitry Andric     case MachineOperand::MO_MCSymbol:
257d8e91e46SDimitry Andric       assert(MO.getTargetFlags() == 0 &&
258d8e91e46SDimitry Andric              "WebAssembly does not use target flags on MCSymbol");
259e6d15924SDimitry Andric       MCOp = lowerSymbolOperand(MO, MO.getMCSymbol());
260dd58ef01SDimitry Andric       break;
261dd58ef01SDimitry Andric     }
262dd58ef01SDimitry Andric 
263dd58ef01SDimitry Andric     OutMI.addOperand(MCOp);
264dd58ef01SDimitry Andric   }
265d8e91e46SDimitry Andric 
266d8e91e46SDimitry Andric   if (!WasmKeepRegisters)
267d8e91e46SDimitry Andric     removeRegisterOperands(MI, OutMI);
268cfca06d7SDimitry Andric   else if (Desc.variadicOpsAreDefs())
269cfca06d7SDimitry Andric     OutMI.insert(OutMI.begin(), MCOperand::createImm(MI->getNumExplicitDefs()));
270d8e91e46SDimitry Andric }
271d8e91e46SDimitry Andric 
removeRegisterOperands(const MachineInstr * MI,MCInst & OutMI)272d8e91e46SDimitry Andric static void removeRegisterOperands(const MachineInstr *MI, MCInst &OutMI) {
273d8e91e46SDimitry Andric   // Remove all uses of stackified registers to bring the instruction format
274d8e91e46SDimitry Andric   // into its final stack form used thruout MC, and transition opcodes to
275d8e91e46SDimitry Andric   // their _S variant.
276cfca06d7SDimitry Andric   // We do this separate from the above code that still may need these
277d8e91e46SDimitry Andric   // registers for e.g. call_indirect signatures.
278d8e91e46SDimitry Andric   // See comments in lib/Target/WebAssembly/WebAssemblyInstrFormats.td for
279d8e91e46SDimitry Andric   // details.
280d8e91e46SDimitry Andric   // TODO: the code above creates new registers which are then removed here.
281d8e91e46SDimitry Andric   // That code could be slightly simplified by not doing that, though maybe
282d8e91e46SDimitry Andric   // it is simpler conceptually to keep the code above in "register mode"
283d8e91e46SDimitry Andric   // until this transition point.
284d8e91e46SDimitry Andric   // FIXME: we are not processing inline assembly, which contains register
285d8e91e46SDimitry Andric   // operands, because it is used by later target generic code.
286d8e91e46SDimitry Andric   if (MI->isDebugInstr() || MI->isLabel() || MI->isInlineAsm())
287d8e91e46SDimitry Andric     return;
288d8e91e46SDimitry Andric 
289d8e91e46SDimitry Andric   // Transform to _S instruction.
290d8e91e46SDimitry Andric   auto RegOpcode = OutMI.getOpcode();
291d8e91e46SDimitry Andric   auto StackOpcode = WebAssembly::getStackOpcode(RegOpcode);
292d8e91e46SDimitry Andric   assert(StackOpcode != -1 && "Failed to stackify instruction");
293d8e91e46SDimitry Andric   OutMI.setOpcode(StackOpcode);
294d8e91e46SDimitry Andric 
295d8e91e46SDimitry Andric   // Remove register operands.
296d8e91e46SDimitry Andric   for (auto I = OutMI.getNumOperands(); I; --I) {
297d8e91e46SDimitry Andric     auto &MO = OutMI.getOperand(I - 1);
298d8e91e46SDimitry Andric     if (MO.isReg()) {
299d8e91e46SDimitry Andric       OutMI.erase(&MO);
300d8e91e46SDimitry Andric     }
301d8e91e46SDimitry Andric   }
302dd58ef01SDimitry Andric }
303