/frameworks/compile/mclinker/lib/MC/MCLinker.cpp
C++ | 718 lines | 481 code | 105 blank | 132 comment | 107 complexity | eb29ced25ff9d88aa529943840c3e351 MD5 | raw file
- //===- MCLinker.cpp -------------------------------------------------------===//
- //
- // The MCLinker Project
- //
- // This file is distributed under the University of Illinois Open Source
- // License. See LICENSE.TXT for details.
- //
- //===----------------------------------------------------------------------===//
- //
- // This file implements the MCLinker class
- //
- //===----------------------------------------------------------------------===//
- #include <mcld/MC/MCLinker.h>
- #include <llvm/Support/Host.h>
- #include <llvm/Support/raw_ostream.h>
- #include <mcld/MC/MCLDInput.h>
- #include <mcld/MC/MCLDInfo.h>
- #include <mcld/LD/Resolver.h>
- #include <mcld/LD/LDContext.h>
- #include <mcld/LD/LDSymbol.h>
- #include <mcld/LD/LDSectionFactory.h>
- #include <mcld/LD/SectionMap.h>
- #include <mcld/LD/RelocationFactory.h>
- #include <mcld/LD/FillFragment.h>
- #include <mcld/LD/RegionFragment.h>
- #include <mcld/LD/EhFrame.h>
- #include <mcld/LD/EhFrameHdr.h>
- #include <mcld/Support/MemoryRegion.h>
- #include <mcld/Support/MsgHandling.h>
- #include <mcld/Target/TargetLDBackend.h>
- using namespace mcld;
- /// Constructor
- MCLinker::MCLinker(TargetLDBackend& pBackend,
- MCLDInfo& pInfo,
- SectionMap& pSectionMap)
- : m_Backend(pBackend),
- m_LDInfo(pInfo),
- m_SectionMap(pSectionMap),
- m_LDSymbolFactory(128),
- m_LDSectHdrFactory(10), // the average number of sections. (assuming 10.)
- m_LDSectDataFactory(10),
- m_pSectionMerger(NULL)
- {
- }
- /// Destructor
- MCLinker::~MCLinker()
- {
- if (NULL != m_pSectionMerger)
- delete m_pSectionMerger;
- }
- //===----------------------------------------------------------------------===//
- // Symbol Operations
- //===----------------------------------------------------------------------===//
- /// addSymbolFromObject - add a symbol from object file and resolve it
- /// immediately
- LDSymbol* MCLinker::addSymbolFromObject(const llvm::StringRef& pName,
- ResolveInfo::Type pType,
- ResolveInfo::Desc pDesc,
- ResolveInfo::Binding pBinding,
- ResolveInfo::SizeType pSize,
- LDSymbol::ValueType pValue,
- FragmentRef* pFragmentRef,
- ResolveInfo::Visibility pVisibility)
- {
- // resolved_result is a triple <resolved_info, existent, override>
- Resolver::Result resolved_result;
- ResolveInfo old_info; // used for arrange output symbols
- if (pBinding == ResolveInfo::Local) {
- // if the symbol is a local symbol, create a LDSymbol for input, but do not
- // resolve them.
- resolved_result.info = m_LDInfo.getNamePool().createSymbol(pName,
- false,
- pType,
- pDesc,
- pBinding,
- pSize,
- pVisibility);
- // No matter if there is a symbol with the same name, insert the symbol
- // into output symbol table. So, we let the existent false.
- resolved_result.existent = false;
- resolved_result.overriden = true;
- }
- else {
- // if the symbol is not local, insert and resolve it immediately
- m_LDInfo.getNamePool().insertSymbol(pName, false, pType, pDesc, pBinding,
- pSize, pVisibility,
- &old_info, resolved_result);
- }
- // the return ResolveInfo should not NULL
- assert(NULL != resolved_result.info);
- // create a LDSymbol for the input file.
- LDSymbol* input_sym = m_LDSymbolFactory.allocate();
- new (input_sym) LDSymbol();
- // set the relation between input LDSymbol and its ResolveInfo
- input_sym->setResolveInfo(*resolved_result.info);
- // set up input LDSymbol
- input_sym->setFragmentRef(pFragmentRef);
- input_sym->setValue(pValue);
- LDSymbol* output_sym = resolved_result.info->outSymbol();
- bool has_output_sym = (NULL != output_sym);
- if (!resolved_result.existent || !has_output_sym) {
- // it is a new symbol, the output_sym should be NULL.
- assert(NULL == output_sym);
- // if it is a new symbol, create a LDSymbol for the output
- output_sym = m_LDSymbolFactory.allocate();
- new (output_sym) LDSymbol();
- // set up the relation between output LDSymbol and its ResolveInfo
- output_sym->setResolveInfo(*resolved_result.info);
- resolved_result.info->setSymPtr(output_sym);
- }
- if (resolved_result.overriden || !has_output_sym) {
- // symbol can be overriden only if it exists.
- assert(output_sym != NULL);
- // should override output LDSymbol
- output_sym->setFragmentRef(pFragmentRef);
- output_sym->setValue(pValue);
- }
- // After symbol resolution, visibility is changed to the most restrict one.
- // we need to arrange its position in the output symbol .
- if (pType != ResolveInfo::Section) {
- if (!has_output_sym) {
- // We merge sections when reading them. So we do not need to output symbols
- // with section type
- // No matter the symbol is already in the output or not, add it if it
- // should be forcefully set local.
- if (shouldForceLocal(*resolved_result.info))
- m_OutputSymbols.forceLocal(*output_sym);
- else {
- // the symbol should not be forcefully local.
- m_OutputSymbols.add(*output_sym);
- }
- }
- else if (resolved_result.overriden) {
- if (!shouldForceLocal(old_info) ||
- !shouldForceLocal(*resolved_result.info)) {
- // If the old info and the new info are both forcefully local, then
- // we should keep the output_sym in forcefully local category. Else,
- // we should re-sort the output_sym
- m_OutputSymbols.arrange(*output_sym, old_info);
- }
- }
- }
- return input_sym;
- }
- /// addSymbolFromDynObj - add a symbol from object file and resolve it
- /// immediately
- LDSymbol* MCLinker::addSymbolFromDynObj(const llvm::StringRef& pName,
- ResolveInfo::Type pType,
- ResolveInfo::Desc pDesc,
- ResolveInfo::Binding pBinding,
- ResolveInfo::SizeType pSize,
- LDSymbol::ValueType pValue,
- FragmentRef* pFragmentRef,
- ResolveInfo::Visibility pVisibility)
- {
- // We merge sections when reading them. So we do not need symbols with
- // section type
- if (pType == ResolveInfo::Section)
- return NULL;
- // ignore symbols with local binding or that have internal or hidden
- // visibility
- if (pBinding == ResolveInfo::Local ||
- pVisibility == ResolveInfo::Internal ||
- pVisibility == ResolveInfo::Hidden)
- return NULL;
- // A protected symbol in a shared library must be treated as a
- // normal symbol when viewed from outside the shared library.
- if (pVisibility == ResolveInfo::Protected)
- pVisibility = ResolveInfo::Default;
- // insert symbol and resolve it immediately
- // resolved_result is a triple <resolved_info, existent, override>
- Resolver::Result resolved_result;
- m_LDInfo.getNamePool().insertSymbol(pName, true, pType, pDesc,
- pBinding, pSize, pVisibility,
- NULL, resolved_result);
- // the return ResolveInfo should not NULL
- assert(NULL != resolved_result.info);
- // create a LDSymbol for the input file.
- LDSymbol* input_sym = m_LDSymbolFactory.allocate();
- new (input_sym) LDSymbol();
- // set up the relation between input LDSymbol and its ResolveInfo
- input_sym->setResolveInfo(*resolved_result.info);
- // set up input LDSymbol
- input_sym->setFragmentRef(pFragmentRef);
- input_sym->setValue(pValue);
- LDSymbol* output_sym = NULL;
- if (!resolved_result.existent) {
- // we get a new symbol, leave it as NULL
- resolved_result.info->setSymPtr(NULL);
- }
- else {
- // we saw the symbol before, but the output_sym still may be NULL.
- output_sym = resolved_result.info->outSymbol();
- }
- if (output_sym != NULL) {
- // After symbol resolution, visibility is changed to the most restrict one.
- // If we are not doing incremental linking, then any symbol with hidden
- // or internal visibility is forcefully set as a local symbol.
- if (shouldForceLocal(*resolved_result.info)) {
- m_OutputSymbols.forceLocal(*output_sym);
- }
- }
- return input_sym;
- }
- /// defineSymbolForcefully - define an output symbol and override it immediately
- LDSymbol* MCLinker::defineSymbolForcefully(const llvm::StringRef& pName,
- bool pIsDyn,
- ResolveInfo::Type pType,
- ResolveInfo::Desc pDesc,
- ResolveInfo::Binding pBinding,
- ResolveInfo::SizeType pSize,
- LDSymbol::ValueType pValue,
- FragmentRef* pFragmentRef,
- ResolveInfo::Visibility pVisibility)
- {
- ResolveInfo* info = m_LDInfo.getNamePool().findInfo(pName);
- LDSymbol* output_sym = NULL;
- if (NULL == info) {
- // the symbol is not in the pool, create a new one.
- // create a ResolveInfo
- Resolver::Result result;
- m_LDInfo.getNamePool().insertSymbol(pName, pIsDyn, pType, pDesc,
- pBinding, pSize, pVisibility,
- NULL, result);
- assert(!result.existent);
- // create a output LDSymbol
- output_sym = m_LDSymbolFactory.allocate();
- new (output_sym) LDSymbol();
- output_sym->setResolveInfo(*result.info);
- result.info->setSymPtr(output_sym);
- if (shouldForceLocal(*result.info))
- m_OutputSymbols.forceLocal(*output_sym);
- else
- m_OutputSymbols.add(*output_sym);
- }
- else {
- // the symbol is already in the pool, override it
- ResolveInfo old_info;
- old_info.override(*info);
- info->setSource(pIsDyn);
- info->setType(pType);
- info->setDesc(pDesc);
- info->setBinding(pBinding);
- info->setVisibility(pVisibility);
- info->setIsSymbol(true);
- info->setSize(pSize);
- output_sym = info->outSymbol();
- if (NULL != output_sym)
- m_OutputSymbols.arrange(*output_sym, old_info);
- else {
- // create a output LDSymbol
- output_sym = m_LDSymbolFactory.allocate();
- new (output_sym) LDSymbol();
- output_sym->setResolveInfo(*info);
- info->setSymPtr(output_sym);
- m_OutputSymbols.add(*output_sym);
- }
- }
- if (NULL != output_sym) {
- output_sym->setFragmentRef(pFragmentRef);
- output_sym->setValue(pValue);
- }
- return output_sym;
- }
- /// defineSymbolAsRefered - define an output symbol and override it immediately
- LDSymbol* MCLinker::defineSymbolAsRefered(const llvm::StringRef& pName,
- bool pIsDyn,
- ResolveInfo::Type pType,
- ResolveInfo::Desc pDesc,
- ResolveInfo::Binding pBinding,
- ResolveInfo::SizeType pSize,
- LDSymbol::ValueType pValue,
- FragmentRef* pFragmentRef,
- ResolveInfo::Visibility pVisibility)
- {
- ResolveInfo* info = m_LDInfo.getNamePool().findInfo(pName);
- if (NULL == info || !(info->isUndef() || info->isDyn())) {
- // only undefined symbol and dynamic symbol can make a reference.
- return NULL;
- }
- // the symbol is already in the pool, override it
- ResolveInfo old_info;
- old_info.override(*info);
- info->setSource(pIsDyn);
- info->setType(pType);
- info->setDesc(pDesc);
- info->setBinding(pBinding);
- info->setVisibility(pVisibility);
- info->setIsSymbol(true);
- info->setSize(pSize);
- LDSymbol* output_sym = info->outSymbol();
- if (NULL != output_sym) {
- output_sym->setFragmentRef(pFragmentRef);
- output_sym->setValue(pValue);
- m_OutputSymbols.arrange(*output_sym, old_info);
- }
- else {
- // create a output LDSymbol
- output_sym = m_LDSymbolFactory.allocate();
- new (output_sym) LDSymbol();
- output_sym->setResolveInfo(*info);
- info->setSymPtr(output_sym);
- m_OutputSymbols.add(*output_sym);
- }
- return output_sym;
- }
- /// defineAndResolveSymbolForcefully - define an output symbol and resolve it
- /// immediately
- LDSymbol* MCLinker::defineAndResolveSymbolForcefully(const llvm::StringRef& pName,
- bool pIsDyn,
- ResolveInfo::Type pType,
- ResolveInfo::Desc pDesc,
- ResolveInfo::Binding pBinding,
- ResolveInfo::SizeType pSize,
- LDSymbol::ValueType pValue,
- FragmentRef* pFragmentRef,
- ResolveInfo::Visibility pVisibility)
- {
- // Result is <info, existent, override>
- Resolver::Result result;
- ResolveInfo old_info;
- m_LDInfo.getNamePool().insertSymbol(pName, pIsDyn, pType, pDesc, pBinding,
- pSize, pVisibility,
- &old_info, result);
- LDSymbol* output_sym = result.info->outSymbol();
- bool has_output_sym = (NULL != output_sym);
- if (!result.existent || !has_output_sym) {
- output_sym = m_LDSymbolFactory.allocate();
- new (output_sym) LDSymbol();
- output_sym->setResolveInfo(*result.info);
- result.info->setSymPtr(output_sym);
- }
- if (result.overriden || !has_output_sym) {
- output_sym->setFragmentRef(pFragmentRef);
- output_sym->setValue(pValue);
- }
- // After symbol resolution, the visibility is changed to the most restrict.
- // arrange the output position
- if (shouldForceLocal(*result.info))
- m_OutputSymbols.forceLocal(*output_sym);
- else if (has_output_sym)
- m_OutputSymbols.arrange(*output_sym, old_info);
- else
- m_OutputSymbols.add(*output_sym);
- return output_sym;
- }
- /// defineAndResolveSymbolAsRefered - define an output symbol and resolve it
- /// immediately.
- LDSymbol* MCLinker::defineAndResolveSymbolAsRefered(const llvm::StringRef& pName,
- bool pIsDyn,
- ResolveInfo::Type pType,
- ResolveInfo::Desc pDesc,
- ResolveInfo::Binding pBinding,
- ResolveInfo::SizeType pSize,
- LDSymbol::ValueType pValue,
- FragmentRef* pFragmentRef,
- ResolveInfo::Visibility pVisibility)
- {
- ResolveInfo* info = m_LDInfo.getNamePool().findInfo(pName);
- if (NULL == info || !(info->isUndef() || info->isDyn())) {
- // only undefined symbol and dynamic symbol can make a reference.
- return NULL;
- }
- return defineAndResolveSymbolForcefully(pName,
- pIsDyn,
- pType,
- pDesc,
- pBinding,
- pSize,
- pValue,
- pFragmentRef,
- pVisibility);
- }
- bool MCLinker::finalizeSymbols()
- {
- SymbolCategory::iterator symbol, symEnd = m_OutputSymbols.end();
- for (symbol = m_OutputSymbols.begin(); symbol != symEnd; ++symbol) {
- if ((*symbol)->resolveInfo()->isAbsolute() ||
- (*symbol)->resolveInfo()->type() == ResolveInfo::File) {
- // absolute symbols or symbols with function type should have
- // zero value
- (*symbol)->setValue(0x0);
- continue;
- }
- if ((*symbol)->hasFragRef()) {
- // set the virtual address of the symbol. If the output file is
- // relocatable object file, the section's virtual address becomes zero.
- // And the symbol's value become section relative offset.
- uint64_t value = getLayout().getOutputOffset(*(*symbol)->fragRef());
- assert(NULL != (*symbol)->fragRef()->frag());
- uint64_t addr = getLayout().getOutputLDSection(*(*symbol)->fragRef()->frag())->addr();
- (*symbol)->setValue(value + addr);
- continue;
- }
- }
- // finialize target-dependent symbols
- return m_Backend.finalizeSymbols(*this, m_LDInfo.output());
- }
- bool MCLinker::shouldForceLocal(const ResolveInfo& pInfo) const
- {
- // forced local symbol matches all rules:
- // 1. We are not doing incremental linking.
- // 2. The symbol is with Hidden or Internal visibility.
- // 3. The symbol should be global or weak. Otherwise, local symbol is local.
- // 4. The symbol is defined or common
- if (m_LDInfo.output().type() != Output::Object &&
- (pInfo.visibility() == ResolveInfo::Hidden ||
- pInfo.visibility() == ResolveInfo::Internal) &&
- (pInfo.isGlobal() || pInfo.isWeak()) &&
- (pInfo.isDefine() || pInfo.isCommon()))
- return true;
- return false;
- }
- //===----------------------------------------------------------------------===//
- // Section Operations
- //===----------------------------------------------------------------------===//
- /// createSectHdr - create the input section header
- LDSection& MCLinker::createSectHdr(const std::string& pName,
- LDFileFormat::Kind pKind,
- uint32_t pType,
- uint32_t pFlag)
- {
- assert(m_LDInfo.output().hasContext());
- // for user such as reader, standard/target fromat
- LDSection* result =
- m_LDSectHdrFactory.produce(pName, pKind, pType, pFlag);
- // check if we need to create a output section for output LDContext
- std::string sect_name = m_SectionMap.getOutputSectName(pName);
- LDSection* output_sect = m_LDInfo.output().context()->getSection(sect_name);
- if (NULL == output_sect) {
- // create a output section and push it into output LDContext
- output_sect =
- m_LDSectHdrFactory.produce(sect_name, pKind, pType, pFlag);
- m_LDInfo.output().context()->getSectionTable().push_back(output_sect);
- m_pSectionMerger->addMapping(pName, output_sect);
- }
- return *result;
- }
- /// getOrCreateOutputSectHdr - for reader and standard/target format to get
- /// or create the output's section header
- LDSection& MCLinker::getOrCreateOutputSectHdr(const std::string& pName,
- LDFileFormat::Kind pKind,
- uint32_t pType,
- uint32_t pFlag,
- uint32_t pAlign)
- {
- assert(m_LDInfo.output().hasContext());
- // check if we need to create a output section for output LDContext
- std::string sect_name = m_SectionMap.getOutputSectName(pName);
- LDSection* output_sect = m_LDInfo.output().context()->getSection(sect_name);
- if (NULL == output_sect) {
- // create a output section and push it into output LDContext
- output_sect =
- m_LDSectHdrFactory.produce(sect_name, pKind, pType, pFlag);
- output_sect->setAlign(pAlign);
- m_LDInfo.output().context()->getSectionTable().push_back(output_sect);
- m_pSectionMerger->addMapping(pName, output_sect);
- }
- return *output_sect;
- }
- /// getOrCreateSectData - get or create SectionData
- /// pSection is input LDSection
- SectionData& MCLinker::getOrCreateSectData(LDSection& pSection)
- {
- // if there is already a section data pointed by section, return it.
- SectionData* sect_data = pSection.getSectionData();
- if (NULL != sect_data) {
- m_Layout.addInputRange(*sect_data, pSection);
- return *sect_data;
- }
- // try to get one from output LDSection
- LDSection* output_sect =
- m_pSectionMerger->getOutputSectHdr(pSection.name());
- assert(NULL != output_sect);
- sect_data = output_sect->getSectionData();
- if (NULL != sect_data) {
- pSection.setSectionData(sect_data);
- m_Layout.addInputRange(*sect_data, pSection);
- return *sect_data;
- }
- // if the output LDSection also has no SectionData, then create one.
- sect_data = m_LDSectDataFactory.allocate();
- new (sect_data) SectionData(*output_sect);
- pSection.setSectionData(sect_data);
- output_sect->setSectionData(sect_data);
- m_Layout.addInputRange(*sect_data, pSection);
- return *sect_data;
- }
- void MCLinker::initSectionMap()
- {
- assert(m_LDInfo.output().hasContext());
- if (NULL == m_pSectionMerger)
- m_pSectionMerger = new SectionMerger(m_SectionMap, *m_LDInfo.output().context());
- }
- bool MCLinker::layout()
- {
- return m_Layout.layout(m_LDInfo.output(), m_Backend, m_LDInfo);
- }
- //===----------------------------------------------------------------------===//
- // Relocation Operations
- //===----------------------------------------------------------------------===//
- /// addRelocation - add a relocation entry in MCLinker (only for object file)
- ///
- /// All symbols should be read and resolved before calling this function.
- Relocation* MCLinker::addRelocation(Relocation::Type pType,
- const LDSymbol& pSym,
- ResolveInfo& pResolveInfo,
- FragmentRef& pFragmentRef,
- const LDSection& pSection,
- Relocation::Address pAddend)
- {
- // FIXME: we should dicard sections and symbols first instead
- // if the symbol is in the discarded input section, then we also need to
- // discard this relocation.
- if (pSym.fragRef() == NULL &&
- pResolveInfo.type() == ResolveInfo::Section &&
- pResolveInfo.desc() == ResolveInfo::Undefined)
- return NULL;
- Relocation* relocation = m_Backend.getRelocFactory()->produce(pType,
- pFragmentRef,
- pAddend);
- relocation->setSymInfo(&pResolveInfo);
- m_RelocationList.push_back(relocation);
- m_Backend.scanRelocation(*relocation, pSym, *this, m_LDInfo,
- m_LDInfo.output(), pSection);
- if (pResolveInfo.isUndef() && !pResolveInfo.isDyn() && !pResolveInfo.isWeak())
- fatal(diag::undefined_reference) << pResolveInfo.name();
- return relocation;
- }
- bool MCLinker::applyRelocations()
- {
- RelocationListType::iterator relocIter, relocEnd = m_RelocationList.end();
- for (relocIter = m_RelocationList.begin(); relocIter != relocEnd; ++relocIter) {
- Fragment* frag = (Fragment*)relocIter;
- static_cast<Relocation*>(frag)->apply(*m_Backend.getRelocFactory(), m_LDInfo);
- }
- return true;
- }
- void MCLinker::syncRelocationResult()
- {
- MemoryRegion* region = m_LDInfo.output().memArea()->request(0,
- m_LDInfo.output().memArea()->handler()->size());
- uint8_t* data = region->getBuffer();
- RelocationListType::iterator relocIter, relocEnd = m_RelocationList.end();
- for (relocIter = m_RelocationList.begin(); relocIter != relocEnd; ++relocIter) {
- Fragment* frag = (Fragment*)relocIter;
- Relocation* reloc = static_cast<Relocation*>(frag);
- // get output file offset
- size_t out_offset = m_Layout.getOutputLDSection(*reloc->targetRef().frag())->offset() +
- m_Layout.getOutputOffset(reloc->targetRef());
- uint8_t* target_addr = data + out_offset;
- // byte swapping if target and host has different endian, and then write back
- if(llvm::sys::isLittleEndianHost() != m_Backend.isLittleEndian()) {
- uint64_t tmp_data = 0;
- switch(m_Backend.bitclass()) {
- case 32u:
- tmp_data = bswap32(reloc->target());
- std::memcpy(target_addr, &tmp_data, 4);
- break;
- case 64u:
- tmp_data = bswap64(reloc->target());
- std::memcpy(target_addr, &tmp_data, 8);
- break;
- default:
- break;
- }
- }
- else {
- std::memcpy(target_addr, &reloc->target(), m_Backend.bitclass()/8);
- }
- } // end of for
- m_LDInfo.output().memArea()->clear();
- }
- //===----------------------------------------------------------------------===//
- // Exception Handling Operations
- //===----------------------------------------------------------------------===//
- /// addEhFrame - add an exception handling section
- /// @param pSection - the input section
- /// @param pArea - the memory area which pSection is within.
- uint64_t MCLinker::addEhFrame(const Input& pInput,
- LDSection& pSection,
- MemoryArea& pArea)
- {
- uint64_t size = 0;
- // get the SectionData of this eh_frame
- SectionData& sect_data = getOrCreateSectData(pSection);
- // parse the eh_frame if the option --eh-frame-hdr is given
- if (m_LDInfo.options().hasEhFrameHdr()) {
- EhFrame* ehframe = m_Backend.getEhFrame();
- assert(NULL != ehframe);
- if (ehframe->canRecognizeAllEhFrame()) {
- size = ehframe->readEhFrame(m_Layout, m_Backend, sect_data, pInput,
- pSection, pArea);
- // zero size indicate that this is an empty section or we can't recognize
- // this eh_frame, handle it as a regular section.
- if (0 != size)
- return size;
- }
- }
- // handle eh_frame as a regular section
- MemoryRegion* region = pArea.request(pInput.fileOffset() + pSection.offset(),
- pSection.size());
- Fragment* frag = NULL;
- if (NULL == region) {
- // If the input section's size is zero, we got a NULL region.
- // use a virtual fill fragment
- frag = new FillFragment(0x0, 0, 0);
- }
- else
- frag = new RegionFragment(*region);
- size = m_Layout.appendFragment(*frag, sect_data, pSection.align());
- return size;
- }