xref: /freebsd-src/contrib/llvm-project/llvm/lib/IR/InlineAsm.cpp (revision bdd1243df58e60e85101c09001d9812a789b6bc4)
10b57cec5SDimitry Andric //===- InlineAsm.cpp - Implement the InlineAsm class ----------------------===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric // This file implements the InlineAsm class.
100b57cec5SDimitry Andric //
110b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
120b57cec5SDimitry Andric 
130b57cec5SDimitry Andric #include "llvm/IR/InlineAsm.h"
140b57cec5SDimitry Andric #include "ConstantsContext.h"
150b57cec5SDimitry Andric #include "LLVMContextImpl.h"
160b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h"
170b57cec5SDimitry Andric #include "llvm/IR/DerivedTypes.h"
180b57cec5SDimitry Andric #include "llvm/IR/LLVMContext.h"
190b57cec5SDimitry Andric #include "llvm/IR/Value.h"
200b57cec5SDimitry Andric #include "llvm/Support/Casting.h"
210b57cec5SDimitry Andric #include "llvm/Support/Compiler.h"
22753f127fSDimitry Andric #include "llvm/Support/Errc.h"
230b57cec5SDimitry Andric #include <algorithm>
240b57cec5SDimitry Andric #include <cassert>
250b57cec5SDimitry Andric #include <cctype>
260b57cec5SDimitry Andric #include <cstdlib>
270b57cec5SDimitry Andric 
280b57cec5SDimitry Andric using namespace llvm;
290b57cec5SDimitry Andric 
InlineAsm(FunctionType * FTy,const std::string & asmString,const std::string & constraints,bool hasSideEffects,bool isAlignStack,AsmDialect asmDialect,bool canThrow)300b57cec5SDimitry Andric InlineAsm::InlineAsm(FunctionType *FTy, const std::string &asmString,
310b57cec5SDimitry Andric                      const std::string &constraints, bool hasSideEffects,
32fe6060f1SDimitry Andric                      bool isAlignStack, AsmDialect asmDialect, bool canThrow)
330b57cec5SDimitry Andric     : Value(PointerType::getUnqual(FTy), Value::InlineAsmVal),
340b57cec5SDimitry Andric       AsmString(asmString), Constraints(constraints), FTy(FTy),
350b57cec5SDimitry Andric       HasSideEffects(hasSideEffects), IsAlignStack(isAlignStack),
36fe6060f1SDimitry Andric       Dialect(asmDialect), CanThrow(canThrow) {
37753f127fSDimitry Andric #ifndef NDEBUG
380b57cec5SDimitry Andric   // Do various checks on the constraint string and type.
39753f127fSDimitry Andric   cantFail(verify(getFunctionType(), constraints));
40753f127fSDimitry Andric #endif
410b57cec5SDimitry Andric }
420b57cec5SDimitry Andric 
get(FunctionType * FTy,StringRef AsmString,StringRef Constraints,bool hasSideEffects,bool isAlignStack,AsmDialect asmDialect,bool canThrow)430b57cec5SDimitry Andric InlineAsm *InlineAsm::get(FunctionType *FTy, StringRef AsmString,
440b57cec5SDimitry Andric                           StringRef Constraints, bool hasSideEffects,
45fe6060f1SDimitry Andric                           bool isAlignStack, AsmDialect asmDialect,
46fe6060f1SDimitry Andric                           bool canThrow) {
470b57cec5SDimitry Andric   InlineAsmKeyType Key(AsmString, Constraints, FTy, hasSideEffects,
48fe6060f1SDimitry Andric                        isAlignStack, asmDialect, canThrow);
490b57cec5SDimitry Andric   LLVMContextImpl *pImpl = FTy->getContext().pImpl;
500b57cec5SDimitry Andric   return pImpl->InlineAsms.getOrCreate(PointerType::getUnqual(FTy), Key);
510b57cec5SDimitry Andric }
520b57cec5SDimitry Andric 
destroyConstant()530b57cec5SDimitry Andric void InlineAsm::destroyConstant() {
540b57cec5SDimitry Andric   getType()->getContext().pImpl->InlineAsms.remove(this);
550b57cec5SDimitry Andric   delete this;
560b57cec5SDimitry Andric }
570b57cec5SDimitry Andric 
getFunctionType() const580b57cec5SDimitry Andric FunctionType *InlineAsm::getFunctionType() const {
590b57cec5SDimitry Andric   return FTy;
600b57cec5SDimitry Andric }
610b57cec5SDimitry Andric 
collectAsmStrs(SmallVectorImpl<StringRef> & AsmStrs) const62*bdd1243dSDimitry Andric void InlineAsm::collectAsmStrs(SmallVectorImpl<StringRef> &AsmStrs) const {
63*bdd1243dSDimitry Andric   StringRef AsmStr(AsmString);
64*bdd1243dSDimitry Andric   AsmStrs.clear();
65*bdd1243dSDimitry Andric 
66*bdd1243dSDimitry Andric   // TODO: 1) Unify delimiter for inline asm, we also meet other delimiters
67*bdd1243dSDimitry Andric   // for example "\0A", ";".
68*bdd1243dSDimitry Andric   // 2) Enhance StringRef. Some of the special delimiter ("\0") can't be
69*bdd1243dSDimitry Andric   // split in StringRef. Also empty StringRef can not call split (will stuck).
70*bdd1243dSDimitry Andric   if (AsmStr.empty())
71*bdd1243dSDimitry Andric     return;
72*bdd1243dSDimitry Andric   AsmStr.split(AsmStrs, "\n\t", -1, false);
73*bdd1243dSDimitry Andric }
74*bdd1243dSDimitry Andric 
750b57cec5SDimitry Andric /// Parse - Analyze the specified string (e.g. "==&{eax}") and fill in the
760b57cec5SDimitry Andric /// fields in this structure.  If the constraint string is not understood,
770b57cec5SDimitry Andric /// return true, otherwise return false.
Parse(StringRef Str,InlineAsm::ConstraintInfoVector & ConstraintsSoFar)780b57cec5SDimitry Andric bool InlineAsm::ConstraintInfo::Parse(StringRef Str,
790b57cec5SDimitry Andric                      InlineAsm::ConstraintInfoVector &ConstraintsSoFar) {
800b57cec5SDimitry Andric   StringRef::iterator I = Str.begin(), E = Str.end();
810b57cec5SDimitry Andric   unsigned multipleAlternativeCount = Str.count('|') + 1;
820b57cec5SDimitry Andric   unsigned multipleAlternativeIndex = 0;
830b57cec5SDimitry Andric   ConstraintCodeVector *pCodes = &Codes;
840b57cec5SDimitry Andric 
850b57cec5SDimitry Andric   // Initialize
860b57cec5SDimitry Andric   isMultipleAlternative = multipleAlternativeCount > 1;
870b57cec5SDimitry Andric   if (isMultipleAlternative) {
880b57cec5SDimitry Andric     multipleAlternatives.resize(multipleAlternativeCount);
890b57cec5SDimitry Andric     pCodes = &multipleAlternatives[0].Codes;
900b57cec5SDimitry Andric   }
910b57cec5SDimitry Andric   Type = isInput;
920b57cec5SDimitry Andric   isEarlyClobber = false;
930b57cec5SDimitry Andric   MatchingInput = -1;
940b57cec5SDimitry Andric   isCommutative = false;
950b57cec5SDimitry Andric   isIndirect = false;
960b57cec5SDimitry Andric   currentAlternativeIndex = 0;
970b57cec5SDimitry Andric 
980b57cec5SDimitry Andric   // Parse prefixes.
990b57cec5SDimitry Andric   if (*I == '~') {
1000b57cec5SDimitry Andric     Type = isClobber;
1010b57cec5SDimitry Andric     ++I;
1020b57cec5SDimitry Andric 
1030b57cec5SDimitry Andric     // '{' must immediately follow '~'.
1040b57cec5SDimitry Andric     if (I != E && *I != '{')
1050b57cec5SDimitry Andric       return true;
1060b57cec5SDimitry Andric   } else if (*I == '=') {
1070b57cec5SDimitry Andric     ++I;
1080b57cec5SDimitry Andric     Type = isOutput;
109fcaf7f86SDimitry Andric   } else if (*I == '!') {
110fcaf7f86SDimitry Andric     ++I;
111fcaf7f86SDimitry Andric     Type = isLabel;
1120b57cec5SDimitry Andric   }
1130b57cec5SDimitry Andric 
1140b57cec5SDimitry Andric   if (*I == '*') {
1150b57cec5SDimitry Andric     isIndirect = true;
1160b57cec5SDimitry Andric     ++I;
1170b57cec5SDimitry Andric   }
1180b57cec5SDimitry Andric 
1190b57cec5SDimitry Andric   if (I == E) return true;  // Just a prefix, like "==" or "~".
1200b57cec5SDimitry Andric 
1210b57cec5SDimitry Andric   // Parse the modifiers.
1220b57cec5SDimitry Andric   bool DoneWithModifiers = false;
1230b57cec5SDimitry Andric   while (!DoneWithModifiers) {
1240b57cec5SDimitry Andric     switch (*I) {
1250b57cec5SDimitry Andric     default:
1260b57cec5SDimitry Andric       DoneWithModifiers = true;
1270b57cec5SDimitry Andric       break;
1280b57cec5SDimitry Andric     case '&':     // Early clobber.
1290b57cec5SDimitry Andric       if (Type != isOutput ||      // Cannot early clobber anything but output.
1300b57cec5SDimitry Andric           isEarlyClobber)          // Reject &&&&&&
1310b57cec5SDimitry Andric         return true;
1320b57cec5SDimitry Andric       isEarlyClobber = true;
1330b57cec5SDimitry Andric       break;
1340b57cec5SDimitry Andric     case '%':     // Commutative.
1350b57cec5SDimitry Andric       if (Type == isClobber ||     // Cannot commute clobbers.
1360b57cec5SDimitry Andric           isCommutative)           // Reject %%%%%
1370b57cec5SDimitry Andric         return true;
1380b57cec5SDimitry Andric       isCommutative = true;
1390b57cec5SDimitry Andric       break;
1400b57cec5SDimitry Andric     case '#':     // Comment.
1410b57cec5SDimitry Andric     case '*':     // Register preferencing.
1420b57cec5SDimitry Andric       return true;     // Not supported.
1430b57cec5SDimitry Andric     }
1440b57cec5SDimitry Andric 
1450b57cec5SDimitry Andric     if (!DoneWithModifiers) {
1460b57cec5SDimitry Andric       ++I;
1470b57cec5SDimitry Andric       if (I == E) return true;   // Just prefixes and modifiers!
1480b57cec5SDimitry Andric     }
1490b57cec5SDimitry Andric   }
1500b57cec5SDimitry Andric 
1510b57cec5SDimitry Andric   // Parse the various constraints.
1520b57cec5SDimitry Andric   while (I != E) {
1530b57cec5SDimitry Andric     if (*I == '{') {   // Physical register reference.
1540b57cec5SDimitry Andric       // Find the end of the register name.
1550b57cec5SDimitry Andric       StringRef::iterator ConstraintEnd = std::find(I+1, E, '}');
1560b57cec5SDimitry Andric       if (ConstraintEnd == E) return true;  // "{foo"
1575ffd83dbSDimitry Andric       pCodes->push_back(std::string(StringRef(I, ConstraintEnd + 1 - I)));
1580b57cec5SDimitry Andric       I = ConstraintEnd+1;
1590b57cec5SDimitry Andric     } else if (isdigit(static_cast<unsigned char>(*I))) { // Matching Constraint
1600b57cec5SDimitry Andric       // Maximal munch numbers.
1610b57cec5SDimitry Andric       StringRef::iterator NumStart = I;
1620b57cec5SDimitry Andric       while (I != E && isdigit(static_cast<unsigned char>(*I)))
1630b57cec5SDimitry Andric         ++I;
1645ffd83dbSDimitry Andric       pCodes->push_back(std::string(StringRef(NumStart, I - NumStart)));
1650b57cec5SDimitry Andric       unsigned N = atoi(pCodes->back().c_str());
1660b57cec5SDimitry Andric       // Check that this is a valid matching constraint!
1670b57cec5SDimitry Andric       if (N >= ConstraintsSoFar.size() || ConstraintsSoFar[N].Type != isOutput||
1680b57cec5SDimitry Andric           Type != isInput)
1690b57cec5SDimitry Andric         return true;  // Invalid constraint number.
1700b57cec5SDimitry Andric 
1710b57cec5SDimitry Andric       // If Operand N already has a matching input, reject this.  An output
1720b57cec5SDimitry Andric       // can't be constrained to the same value as multiple inputs.
1730b57cec5SDimitry Andric       if (isMultipleAlternative) {
1740b57cec5SDimitry Andric         if (multipleAlternativeIndex >=
1750b57cec5SDimitry Andric             ConstraintsSoFar[N].multipleAlternatives.size())
1760b57cec5SDimitry Andric           return true;
1770b57cec5SDimitry Andric         InlineAsm::SubConstraintInfo &scInfo =
1780b57cec5SDimitry Andric           ConstraintsSoFar[N].multipleAlternatives[multipleAlternativeIndex];
1790b57cec5SDimitry Andric         if (scInfo.MatchingInput != -1)
1800b57cec5SDimitry Andric           return true;
1810b57cec5SDimitry Andric         // Note that operand #n has a matching input.
1820b57cec5SDimitry Andric         scInfo.MatchingInput = ConstraintsSoFar.size();
1830b57cec5SDimitry Andric         assert(scInfo.MatchingInput >= 0);
1840b57cec5SDimitry Andric       } else {
1850b57cec5SDimitry Andric         if (ConstraintsSoFar[N].hasMatchingInput() &&
1860b57cec5SDimitry Andric             (size_t)ConstraintsSoFar[N].MatchingInput !=
1870b57cec5SDimitry Andric                 ConstraintsSoFar.size())
1880b57cec5SDimitry Andric           return true;
1890b57cec5SDimitry Andric         // Note that operand #n has a matching input.
1900b57cec5SDimitry Andric         ConstraintsSoFar[N].MatchingInput = ConstraintsSoFar.size();
1910b57cec5SDimitry Andric         assert(ConstraintsSoFar[N].MatchingInput >= 0);
1920b57cec5SDimitry Andric         }
1930b57cec5SDimitry Andric     } else if (*I == '|') {
1940b57cec5SDimitry Andric       multipleAlternativeIndex++;
1950b57cec5SDimitry Andric       pCodes = &multipleAlternatives[multipleAlternativeIndex].Codes;
1960b57cec5SDimitry Andric       ++I;
1970b57cec5SDimitry Andric     } else if (*I == '^') {
1980b57cec5SDimitry Andric       // Multi-letter constraint
1990b57cec5SDimitry Andric       // FIXME: For now assuming these are 2-character constraints.
2005ffd83dbSDimitry Andric       pCodes->push_back(std::string(StringRef(I + 1, 2)));
2010b57cec5SDimitry Andric       I += 3;
2028bcb0991SDimitry Andric     } else if (*I == '@') {
2038bcb0991SDimitry Andric       // Multi-letter constraint
2048bcb0991SDimitry Andric       ++I;
2058bcb0991SDimitry Andric       unsigned char C = static_cast<unsigned char>(*I);
2068bcb0991SDimitry Andric       assert(isdigit(C) && "Expected a digit!");
2078bcb0991SDimitry Andric       int N = C - '0';
2088bcb0991SDimitry Andric       assert(N > 0 && "Found a zero letter constraint!");
2098bcb0991SDimitry Andric       ++I;
2105ffd83dbSDimitry Andric       pCodes->push_back(std::string(StringRef(I, N)));
2118bcb0991SDimitry Andric       I += N;
2120b57cec5SDimitry Andric     } else {
2130b57cec5SDimitry Andric       // Single letter constraint.
2145ffd83dbSDimitry Andric       pCodes->push_back(std::string(StringRef(I, 1)));
2150b57cec5SDimitry Andric       ++I;
2160b57cec5SDimitry Andric     }
2170b57cec5SDimitry Andric   }
2180b57cec5SDimitry Andric 
2190b57cec5SDimitry Andric   return false;
2200b57cec5SDimitry Andric }
2210b57cec5SDimitry Andric 
2220b57cec5SDimitry Andric /// selectAlternative - Point this constraint to the alternative constraint
2230b57cec5SDimitry Andric /// indicated by the index.
selectAlternative(unsigned index)2240b57cec5SDimitry Andric void InlineAsm::ConstraintInfo::selectAlternative(unsigned index) {
2250b57cec5SDimitry Andric   if (index < multipleAlternatives.size()) {
2260b57cec5SDimitry Andric     currentAlternativeIndex = index;
2270b57cec5SDimitry Andric     InlineAsm::SubConstraintInfo &scInfo =
2280b57cec5SDimitry Andric       multipleAlternatives[currentAlternativeIndex];
2290b57cec5SDimitry Andric     MatchingInput = scInfo.MatchingInput;
2300b57cec5SDimitry Andric     Codes = scInfo.Codes;
2310b57cec5SDimitry Andric   }
2320b57cec5SDimitry Andric }
2330b57cec5SDimitry Andric 
2340b57cec5SDimitry Andric InlineAsm::ConstraintInfoVector
ParseConstraints(StringRef Constraints)2350b57cec5SDimitry Andric InlineAsm::ParseConstraints(StringRef Constraints) {
2360b57cec5SDimitry Andric   ConstraintInfoVector Result;
2370b57cec5SDimitry Andric 
2380b57cec5SDimitry Andric   // Scan the constraints string.
2390b57cec5SDimitry Andric   for (StringRef::iterator I = Constraints.begin(),
2400b57cec5SDimitry Andric          E = Constraints.end(); I != E; ) {
2410b57cec5SDimitry Andric     ConstraintInfo Info;
2420b57cec5SDimitry Andric 
2430b57cec5SDimitry Andric     // Find the end of this constraint.
2440b57cec5SDimitry Andric     StringRef::iterator ConstraintEnd = std::find(I, E, ',');
2450b57cec5SDimitry Andric 
2460b57cec5SDimitry Andric     if (ConstraintEnd == I ||  // Empty constraint like ",,"
2470b57cec5SDimitry Andric         Info.Parse(StringRef(I, ConstraintEnd-I), Result)) {
2480b57cec5SDimitry Andric       Result.clear();          // Erroneous constraint?
2490b57cec5SDimitry Andric       break;
2500b57cec5SDimitry Andric     }
2510b57cec5SDimitry Andric 
2520b57cec5SDimitry Andric     Result.push_back(Info);
2530b57cec5SDimitry Andric 
2540b57cec5SDimitry Andric     // ConstraintEnd may be either the next comma or the end of the string.  In
2550b57cec5SDimitry Andric     // the former case, we skip the comma.
2560b57cec5SDimitry Andric     I = ConstraintEnd;
2570b57cec5SDimitry Andric     if (I != E) {
2580b57cec5SDimitry Andric       ++I;
2590b57cec5SDimitry Andric       if (I == E) {
2600b57cec5SDimitry Andric         Result.clear();
2610b57cec5SDimitry Andric         break;
2620b57cec5SDimitry Andric       } // don't allow "xyz,"
2630b57cec5SDimitry Andric     }
2640b57cec5SDimitry Andric   }
2650b57cec5SDimitry Andric 
2660b57cec5SDimitry Andric   return Result;
2670b57cec5SDimitry Andric }
2680b57cec5SDimitry Andric 
makeStringError(const char * Msg)269753f127fSDimitry Andric static Error makeStringError(const char *Msg) {
270753f127fSDimitry Andric   return createStringError(errc::invalid_argument, Msg);
271753f127fSDimitry Andric }
272753f127fSDimitry Andric 
verify(FunctionType * Ty,StringRef ConstStr)273753f127fSDimitry Andric Error InlineAsm::verify(FunctionType *Ty, StringRef ConstStr) {
274753f127fSDimitry Andric   if (Ty->isVarArg())
275753f127fSDimitry Andric     return makeStringError("inline asm cannot be variadic");
2760b57cec5SDimitry Andric 
2770b57cec5SDimitry Andric   ConstraintInfoVector Constraints = ParseConstraints(ConstStr);
2780b57cec5SDimitry Andric 
2790b57cec5SDimitry Andric   // Error parsing constraints.
280753f127fSDimitry Andric   if (Constraints.empty() && !ConstStr.empty())
281753f127fSDimitry Andric     return makeStringError("failed to parse constraints");
2820b57cec5SDimitry Andric 
2830b57cec5SDimitry Andric   unsigned NumOutputs = 0, NumInputs = 0, NumClobbers = 0;
284fcaf7f86SDimitry Andric   unsigned NumIndirect = 0, NumLabels = 0;
2850b57cec5SDimitry Andric 
2860eae32dcSDimitry Andric   for (const ConstraintInfo &Constraint : Constraints) {
2870eae32dcSDimitry Andric     switch (Constraint.Type) {
2880b57cec5SDimitry Andric     case InlineAsm::isOutput:
289fcaf7f86SDimitry Andric       if ((NumInputs-NumIndirect) != 0 || NumClobbers != 0 || NumLabels != 0)
290fcaf7f86SDimitry Andric         return makeStringError("output constraint occurs after input, "
291fcaf7f86SDimitry Andric                                "clobber or label constraint");
292753f127fSDimitry Andric 
2930eae32dcSDimitry Andric       if (!Constraint.isIndirect) {
2940b57cec5SDimitry Andric         ++NumOutputs;
2950b57cec5SDimitry Andric         break;
2960b57cec5SDimitry Andric       }
2970b57cec5SDimitry Andric       ++NumIndirect;
298*bdd1243dSDimitry Andric       [[fallthrough]]; // We fall through for Indirect Outputs.
2990b57cec5SDimitry Andric     case InlineAsm::isInput:
300753f127fSDimitry Andric       if (NumClobbers)
301753f127fSDimitry Andric         return makeStringError("input constraint occurs after clobber "
302753f127fSDimitry Andric                                "constraint");
3030b57cec5SDimitry Andric       ++NumInputs;
3040b57cec5SDimitry Andric       break;
3050b57cec5SDimitry Andric     case InlineAsm::isClobber:
3060b57cec5SDimitry Andric       ++NumClobbers;
3070b57cec5SDimitry Andric       break;
308fcaf7f86SDimitry Andric     case InlineAsm::isLabel:
309fcaf7f86SDimitry Andric       if (NumClobbers)
310fcaf7f86SDimitry Andric         return makeStringError("label constraint occurs after clobber "
311fcaf7f86SDimitry Andric                                "constraint");
312fcaf7f86SDimitry Andric 
313fcaf7f86SDimitry Andric       ++NumLabels;
314fcaf7f86SDimitry Andric       break;
3150b57cec5SDimitry Andric     }
3160b57cec5SDimitry Andric   }
3170b57cec5SDimitry Andric 
3180b57cec5SDimitry Andric   switch (NumOutputs) {
3190b57cec5SDimitry Andric   case 0:
320753f127fSDimitry Andric     if (!Ty->getReturnType()->isVoidTy())
321753f127fSDimitry Andric       return makeStringError("inline asm without outputs must return void");
3220b57cec5SDimitry Andric     break;
3230b57cec5SDimitry Andric   case 1:
324753f127fSDimitry Andric     if (Ty->getReturnType()->isStructTy())
325753f127fSDimitry Andric       return makeStringError("inline asm with one output cannot return struct");
3260b57cec5SDimitry Andric     break;
3270b57cec5SDimitry Andric   default:
3280b57cec5SDimitry Andric     StructType *STy = dyn_cast<StructType>(Ty->getReturnType());
3290b57cec5SDimitry Andric     if (!STy || STy->getNumElements() != NumOutputs)
330753f127fSDimitry Andric       return makeStringError("number of output constraints does not match "
331753f127fSDimitry Andric                              "number of return struct elements");
3320b57cec5SDimitry Andric     break;
3330b57cec5SDimitry Andric   }
3340b57cec5SDimitry Andric 
335753f127fSDimitry Andric   if (Ty->getNumParams() != NumInputs)
336753f127fSDimitry Andric     return makeStringError("number of input constraints does not match number "
337753f127fSDimitry Andric                            "of parameters");
338fcaf7f86SDimitry Andric 
339fcaf7f86SDimitry Andric   // We don't have access to labels here, NumLabels will be checked separately.
340753f127fSDimitry Andric   return Error::success();
3410b57cec5SDimitry Andric }
342