1e3b55780SDimitry Andric //===-- AArch64TargetParser - Parser for AArch64 features -------*- C++ -*-===//
2e3b55780SDimitry Andric //
3e3b55780SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e3b55780SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5e3b55780SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e3b55780SDimitry Andric //
7e3b55780SDimitry Andric //===----------------------------------------------------------------------===//
8e3b55780SDimitry Andric //
9e3b55780SDimitry Andric // This file implements a target parser to recognise AArch64 hardware features
10e3b55780SDimitry Andric // such as FPU/CPU/ARCH and extension names.
11e3b55780SDimitry Andric //
12e3b55780SDimitry Andric //===----------------------------------------------------------------------===//
13e3b55780SDimitry Andric
14e3b55780SDimitry Andric #include "llvm/TargetParser/AArch64TargetParser.h"
154df029ccSDimitry Andric #include "llvm/Support/Debug.h"
16b1c73532SDimitry Andric #include "llvm/Support/Format.h"
17b1c73532SDimitry Andric #include "llvm/Support/raw_ostream.h"
18e3b55780SDimitry Andric #include "llvm/TargetParser/ARMTargetParserCommon.h"
19e3b55780SDimitry Andric #include "llvm/TargetParser/Triple.h"
20e3b55780SDimitry Andric #include <cctype>
21ac9a064cSDimitry Andric #include <vector>
22e3b55780SDimitry Andric
234df029ccSDimitry Andric #define DEBUG_TYPE "target-parser"
244df029ccSDimitry Andric
25e3b55780SDimitry Andric using namespace llvm;
26e3b55780SDimitry Andric
27ac9a064cSDimitry Andric #define EMIT_FMV_INFO
28ac9a064cSDimitry Andric #include "llvm/TargetParser/AArch64TargetParserDef.inc"
29ac9a064cSDimitry Andric
checkArchVersion(llvm::StringRef Arch)30e3b55780SDimitry Andric static unsigned checkArchVersion(llvm::StringRef Arch) {
31e3b55780SDimitry Andric if (Arch.size() >= 2 && Arch[0] == 'v' && std::isdigit(Arch[1]))
32e3b55780SDimitry Andric return (Arch[1] - 48);
33e3b55780SDimitry Andric return 0;
34e3b55780SDimitry Andric }
35e3b55780SDimitry Andric
getArchForCpu(StringRef CPU)364df029ccSDimitry Andric const AArch64::ArchInfo *AArch64::getArchForCpu(StringRef CPU) {
37e3b55780SDimitry Andric // Note: this now takes cpu aliases into account
387fa27ce4SDimitry Andric std::optional<CpuInfo> Cpu = parseCpu(CPU);
397fa27ce4SDimitry Andric if (!Cpu)
404df029ccSDimitry Andric return nullptr;
414df029ccSDimitry Andric return &Cpu->Arch;
42e3b55780SDimitry Andric }
43e3b55780SDimitry Andric
findBySubArch(StringRef SubArch)447fa27ce4SDimitry Andric std::optional<AArch64::ArchInfo> AArch64::ArchInfo::findBySubArch(StringRef SubArch) {
45e3b55780SDimitry Andric for (const auto *A : AArch64::ArchInfos)
46e3b55780SDimitry Andric if (A->getSubArch() == SubArch)
47e3b55780SDimitry Andric return *A;
487fa27ce4SDimitry Andric return {};
49e3b55780SDimitry Andric }
50e3b55780SDimitry Andric
getCpuSupportsMask(ArrayRef<StringRef> FeatureStrs)51e3b55780SDimitry Andric uint64_t AArch64::getCpuSupportsMask(ArrayRef<StringRef> FeatureStrs) {
52e3b55780SDimitry Andric uint64_t FeaturesMask = 0;
53e3b55780SDimitry Andric for (const StringRef &FeatureStr : FeatureStrs) {
54ac9a064cSDimitry Andric if (auto Ext = parseFMVExtension(FeatureStr))
55ac9a064cSDimitry Andric FeaturesMask |= (1ULL << Ext->Bit);
56e3b55780SDimitry Andric }
57e3b55780SDimitry Andric return FeaturesMask;
58e3b55780SDimitry Andric }
59e3b55780SDimitry Andric
getExtensionFeatures(const AArch64::ExtensionBitset & InputExts,std::vector<StringRef> & Features)60b1c73532SDimitry Andric bool AArch64::getExtensionFeatures(
61b1c73532SDimitry Andric const AArch64::ExtensionBitset &InputExts,
62e3b55780SDimitry Andric std::vector<StringRef> &Features) {
63e3b55780SDimitry Andric for (const auto &E : Extensions)
64e3b55780SDimitry Andric /* INVALID and NONE have no feature name. */
65ac9a064cSDimitry Andric if (InputExts.test(E.ID) && !E.PosTargetFeature.empty())
66ac9a064cSDimitry Andric Features.push_back(E.PosTargetFeature);
67e3b55780SDimitry Andric
68e3b55780SDimitry Andric return true;
69e3b55780SDimitry Andric }
70e3b55780SDimitry Andric
resolveCPUAlias(StringRef Name)71e3b55780SDimitry Andric StringRef AArch64::resolveCPUAlias(StringRef Name) {
72e3b55780SDimitry Andric for (const auto &A : CpuAliases)
73ac9a064cSDimitry Andric if (A.AltName == Name)
74e3b55780SDimitry Andric return A.Name;
75e3b55780SDimitry Andric return Name;
76e3b55780SDimitry Andric }
77e3b55780SDimitry Andric
getArchExtFeature(StringRef ArchExt)78e3b55780SDimitry Andric StringRef AArch64::getArchExtFeature(StringRef ArchExt) {
794df029ccSDimitry Andric bool IsNegated = ArchExt.starts_with("no");
804df029ccSDimitry Andric StringRef ArchExtBase = IsNegated ? ArchExt.drop_front(2) : ArchExt;
814df029ccSDimitry Andric
824df029ccSDimitry Andric if (auto AE = parseArchExtension(ArchExtBase)) {
83ac9a064cSDimitry Andric assert(!(AE.has_value() && AE->NegTargetFeature.empty()));
84ac9a064cSDimitry Andric return IsNegated ? AE->NegTargetFeature : AE->PosTargetFeature;
85e3b55780SDimitry Andric }
86e3b55780SDimitry Andric
87e3b55780SDimitry Andric return StringRef();
88e3b55780SDimitry Andric }
89e3b55780SDimitry Andric
fillValidCPUArchList(SmallVectorImpl<StringRef> & Values)90e3b55780SDimitry Andric void AArch64::fillValidCPUArchList(SmallVectorImpl<StringRef> &Values) {
91e3b55780SDimitry Andric for (const auto &C : CpuInfos)
92e3b55780SDimitry Andric Values.push_back(C.Name);
93e3b55780SDimitry Andric
94e3b55780SDimitry Andric for (const auto &Alias : CpuAliases)
95ac9a064cSDimitry Andric // The apple-latest alias is backend only, do not expose it to clang's -mcpu.
96ac9a064cSDimitry Andric if (Alias.AltName != "apple-latest")
97ac9a064cSDimitry Andric Values.push_back(Alias.AltName);
98ac9a064cSDimitry Andric
99ac9a064cSDimitry Andric llvm::sort(Values);
100e3b55780SDimitry Andric }
101e3b55780SDimitry Andric
isX18ReservedByDefault(const Triple & TT)102e3b55780SDimitry Andric bool AArch64::isX18ReservedByDefault(const Triple &TT) {
103e3b55780SDimitry Andric return TT.isAndroid() || TT.isOSDarwin() || TT.isOSFuchsia() ||
1047fa27ce4SDimitry Andric TT.isOSWindows() || TT.isOHOSFamily();
105e3b55780SDimitry Andric }
106e3b55780SDimitry Andric
107e3b55780SDimitry Andric // Allows partial match, ex. "v8a" matches "armv8a".
parseArch(StringRef Arch)1084df029ccSDimitry Andric const AArch64::ArchInfo *AArch64::parseArch(StringRef Arch) {
109e3b55780SDimitry Andric Arch = llvm::ARM::getCanonicalArchName(Arch);
110e3b55780SDimitry Andric if (checkArchVersion(Arch) < 8)
1117fa27ce4SDimitry Andric return {};
112e3b55780SDimitry Andric
113e3b55780SDimitry Andric StringRef Syn = llvm::ARM::getArchSynonym(Arch);
114e3b55780SDimitry Andric for (const auto *A : ArchInfos) {
115312c0ed1SDimitry Andric if (A->Name.ends_with(Syn))
1164df029ccSDimitry Andric return A;
117e3b55780SDimitry Andric }
1187fa27ce4SDimitry Andric return {};
119e3b55780SDimitry Andric }
120e3b55780SDimitry Andric
121ac9a064cSDimitry Andric std::optional<AArch64::ExtensionInfo>
parseArchExtension(StringRef ArchExt)122ac9a064cSDimitry Andric AArch64::parseArchExtension(StringRef ArchExt) {
123ac9a064cSDimitry Andric if (ArchExt.empty())
124ac9a064cSDimitry Andric return {};
125e3b55780SDimitry Andric for (const auto &A : Extensions) {
126ac9a064cSDimitry Andric if (ArchExt == A.UserVisibleName || ArchExt == A.Alias)
1277fa27ce4SDimitry Andric return A;
128e3b55780SDimitry Andric }
1297fa27ce4SDimitry Andric return {};
130e3b55780SDimitry Andric }
131e3b55780SDimitry Andric
parseFMVExtension(StringRef FMVExt)132ac9a064cSDimitry Andric std::optional<AArch64::FMVInfo> AArch64::parseFMVExtension(StringRef FMVExt) {
133ac9a064cSDimitry Andric // FIXME introduce general alias functionality, or remove this exception.
134ac9a064cSDimitry Andric if (FMVExt == "rdma")
135ac9a064cSDimitry Andric FMVExt = "rdm";
136ac9a064cSDimitry Andric
137ac9a064cSDimitry Andric for (const auto &I : getFMVInfo()) {
138ac9a064cSDimitry Andric if (FMVExt == I.Name)
139ac9a064cSDimitry Andric return I;
140ac9a064cSDimitry Andric }
141ac9a064cSDimitry Andric return {};
142ac9a064cSDimitry Andric }
143ac9a064cSDimitry Andric
144ac9a064cSDimitry Andric std::optional<AArch64::ExtensionInfo>
targetFeatureToExtension(StringRef TargetFeature)145ac9a064cSDimitry Andric AArch64::targetFeatureToExtension(StringRef TargetFeature) {
146ac9a064cSDimitry Andric for (const auto &E : Extensions)
147ac9a064cSDimitry Andric if (TargetFeature == E.PosTargetFeature)
148ac9a064cSDimitry Andric return E;
149ac9a064cSDimitry Andric return {};
150ac9a064cSDimitry Andric }
151ac9a064cSDimitry Andric
parseCpu(StringRef Name)1527fa27ce4SDimitry Andric std::optional<AArch64::CpuInfo> AArch64::parseCpu(StringRef Name) {
153e3b55780SDimitry Andric // Resolve aliases first.
154e3b55780SDimitry Andric Name = resolveCPUAlias(Name);
155e3b55780SDimitry Andric
156e3b55780SDimitry Andric // Then find the CPU name.
157e3b55780SDimitry Andric for (const auto &C : CpuInfos)
158e3b55780SDimitry Andric if (Name == C.Name)
159e3b55780SDimitry Andric return C;
160e3b55780SDimitry Andric
1617fa27ce4SDimitry Andric return {};
162e3b55780SDimitry Andric }
163b1c73532SDimitry Andric
PrintSupportedExtensions()164ac9a064cSDimitry Andric void AArch64::PrintSupportedExtensions() {
165b1c73532SDimitry Andric outs() << "All available -march extensions for AArch64\n\n"
166b1c73532SDimitry Andric << " " << left_justify("Name", 20)
167ac9a064cSDimitry Andric << left_justify("Architecture Feature(s)", 55)
168ac9a064cSDimitry Andric << "Description\n";
169b1c73532SDimitry Andric for (const auto &Ext : Extensions) {
170b1c73532SDimitry Andric // Extensions without a feature cannot be used with -march.
171ac9a064cSDimitry Andric if (!Ext.UserVisibleName.empty() && !Ext.PosTargetFeature.empty()) {
172b1c73532SDimitry Andric outs() << " "
173ac9a064cSDimitry Andric << format(Ext.Description.empty() ? "%-20s%s\n" : "%-20s%-55s%s\n",
174ac9a064cSDimitry Andric Ext.UserVisibleName.str().c_str(),
175ac9a064cSDimitry Andric Ext.ArchFeatureName.str().c_str(),
176ac9a064cSDimitry Andric Ext.Description.str().c_str());
177b1c73532SDimitry Andric }
178b1c73532SDimitry Andric }
179b1c73532SDimitry Andric }
1804df029ccSDimitry Andric
181ac9a064cSDimitry Andric void
printEnabledExtensions(const std::set<StringRef> & EnabledFeatureNames)182ac9a064cSDimitry Andric AArch64::printEnabledExtensions(const std::set<StringRef> &EnabledFeatureNames) {
183ac9a064cSDimitry Andric outs() << "Extensions enabled for the given AArch64 target\n\n"
184ac9a064cSDimitry Andric << " " << left_justify("Architecture Feature(s)", 55)
185ac9a064cSDimitry Andric << "Description\n";
186ac9a064cSDimitry Andric std::vector<ExtensionInfo> EnabledExtensionsInfo;
187ac9a064cSDimitry Andric for (const auto &FeatureName : EnabledFeatureNames) {
188ac9a064cSDimitry Andric std::string PosFeatureName = '+' + FeatureName.str();
189ac9a064cSDimitry Andric if (auto ExtInfo = targetFeatureToExtension(PosFeatureName))
190ac9a064cSDimitry Andric EnabledExtensionsInfo.push_back(*ExtInfo);
191ac9a064cSDimitry Andric }
192ac9a064cSDimitry Andric
193ac9a064cSDimitry Andric std::sort(EnabledExtensionsInfo.begin(), EnabledExtensionsInfo.end(),
194ac9a064cSDimitry Andric [](const ExtensionInfo &Lhs, const ExtensionInfo &Rhs) {
195ac9a064cSDimitry Andric return Lhs.ArchFeatureName < Rhs.ArchFeatureName;
196ac9a064cSDimitry Andric });
197ac9a064cSDimitry Andric
198ac9a064cSDimitry Andric for (const auto &Ext : EnabledExtensionsInfo) {
199ac9a064cSDimitry Andric outs() << " "
200ac9a064cSDimitry Andric << format("%-55s%s\n",
201ac9a064cSDimitry Andric Ext.ArchFeatureName.str().c_str(),
202ac9a064cSDimitry Andric Ext.Description.str().c_str());
203ac9a064cSDimitry Andric }
204ac9a064cSDimitry Andric }
205ac9a064cSDimitry Andric
2064df029ccSDimitry Andric const llvm::AArch64::ExtensionInfo &
lookupExtensionByID(llvm::AArch64::ArchExtKind ExtID)2074df029ccSDimitry Andric lookupExtensionByID(llvm::AArch64::ArchExtKind ExtID) {
2084df029ccSDimitry Andric for (const auto &E : llvm::AArch64::Extensions)
2094df029ccSDimitry Andric if (E.ID == ExtID)
2104df029ccSDimitry Andric return E;
2114df029ccSDimitry Andric llvm_unreachable("Invalid extension ID");
2124df029ccSDimitry Andric }
2134df029ccSDimitry Andric
enable(ArchExtKind E)2144df029ccSDimitry Andric void AArch64::ExtensionSet::enable(ArchExtKind E) {
2154df029ccSDimitry Andric if (Enabled.test(E))
2164df029ccSDimitry Andric return;
2174df029ccSDimitry Andric
218ac9a064cSDimitry Andric LLVM_DEBUG(llvm::dbgs() << "Enable " << lookupExtensionByID(E).UserVisibleName << "\n");
2194df029ccSDimitry Andric
2204df029ccSDimitry Andric Touched.set(E);
2214df029ccSDimitry Andric Enabled.set(E);
2224df029ccSDimitry Andric
2234df029ccSDimitry Andric // Recursively enable all features that this one depends on. This handles all
2244df029ccSDimitry Andric // of the simple cases, where the behaviour doesn't depend on the base
2254df029ccSDimitry Andric // architecture version.
2264df029ccSDimitry Andric for (auto Dep : ExtensionDependencies)
2274df029ccSDimitry Andric if (E == Dep.Later)
2284df029ccSDimitry Andric enable(Dep.Earlier);
2294df029ccSDimitry Andric
2304df029ccSDimitry Andric // Special cases for dependencies which vary depending on the base
2314df029ccSDimitry Andric // architecture version.
2324df029ccSDimitry Andric if (BaseArch) {
2334df029ccSDimitry Andric // +fp16 implies +fp16fml for v8.4A+, but not v9.0-A+
2344df029ccSDimitry Andric if (E == AEK_FP16 && BaseArch->is_superset(ARMV8_4A) &&
2354df029ccSDimitry Andric !BaseArch->is_superset(ARMV9A))
2364df029ccSDimitry Andric enable(AEK_FP16FML);
2374df029ccSDimitry Andric
2384df029ccSDimitry Andric // For v8.4A+ and v9.0A+, +crypto also enables +sha3 and +sm4.
2394df029ccSDimitry Andric if (E == AEK_CRYPTO && BaseArch->is_superset(ARMV8_4A)) {
2404df029ccSDimitry Andric enable(AEK_SHA3);
2414df029ccSDimitry Andric enable(AEK_SM4);
2424df029ccSDimitry Andric }
2434df029ccSDimitry Andric }
2444df029ccSDimitry Andric }
2454df029ccSDimitry Andric
disable(ArchExtKind E)2464df029ccSDimitry Andric void AArch64::ExtensionSet::disable(ArchExtKind E) {
2474df029ccSDimitry Andric // -crypto always disables aes, sha2, sha3 and sm4, even for architectures
2484df029ccSDimitry Andric // where the latter two would not be enabled by +crypto.
2494df029ccSDimitry Andric if (E == AEK_CRYPTO) {
2504df029ccSDimitry Andric disable(AEK_AES);
2514df029ccSDimitry Andric disable(AEK_SHA2);
2524df029ccSDimitry Andric disable(AEK_SHA3);
2534df029ccSDimitry Andric disable(AEK_SM4);
2544df029ccSDimitry Andric }
2554df029ccSDimitry Andric
2564df029ccSDimitry Andric if (!Enabled.test(E))
2574df029ccSDimitry Andric return;
2584df029ccSDimitry Andric
259ac9a064cSDimitry Andric LLVM_DEBUG(llvm::dbgs() << "Disable " << lookupExtensionByID(E).UserVisibleName << "\n");
2604df029ccSDimitry Andric
2614df029ccSDimitry Andric Touched.set(E);
2624df029ccSDimitry Andric Enabled.reset(E);
2634df029ccSDimitry Andric
2644df029ccSDimitry Andric // Recursively disable all features that depends on this one.
2654df029ccSDimitry Andric for (auto Dep : ExtensionDependencies)
2664df029ccSDimitry Andric if (E == Dep.Earlier)
2674df029ccSDimitry Andric disable(Dep.Later);
2684df029ccSDimitry Andric }
2694df029ccSDimitry Andric
addCPUDefaults(const CpuInfo & CPU)2704df029ccSDimitry Andric void AArch64::ExtensionSet::addCPUDefaults(const CpuInfo &CPU) {
2714df029ccSDimitry Andric LLVM_DEBUG(llvm::dbgs() << "addCPUDefaults(" << CPU.Name << ")\n");
2724df029ccSDimitry Andric BaseArch = &CPU.Arch;
2734df029ccSDimitry Andric
2744df029ccSDimitry Andric AArch64::ExtensionBitset CPUExtensions = CPU.getImpliedExtensions();
2754df029ccSDimitry Andric for (const auto &E : Extensions)
2764df029ccSDimitry Andric if (CPUExtensions.test(E.ID))
2774df029ccSDimitry Andric enable(E.ID);
2784df029ccSDimitry Andric }
2794df029ccSDimitry Andric
addArchDefaults(const ArchInfo & Arch)2804df029ccSDimitry Andric void AArch64::ExtensionSet::addArchDefaults(const ArchInfo &Arch) {
2814df029ccSDimitry Andric LLVM_DEBUG(llvm::dbgs() << "addArchDefaults(" << Arch.Name << ")\n");
2824df029ccSDimitry Andric BaseArch = &Arch;
2834df029ccSDimitry Andric
2844df029ccSDimitry Andric for (const auto &E : Extensions)
2854df029ccSDimitry Andric if (Arch.DefaultExts.test(E.ID))
2864df029ccSDimitry Andric enable(E.ID);
2874df029ccSDimitry Andric }
2884df029ccSDimitry Andric
parseModifier(StringRef Modifier,const bool AllowNoDashForm)289ac9a064cSDimitry Andric bool AArch64::ExtensionSet::parseModifier(StringRef Modifier,
290ac9a064cSDimitry Andric const bool AllowNoDashForm) {
2914df029ccSDimitry Andric LLVM_DEBUG(llvm::dbgs() << "parseModifier(" << Modifier << ")\n");
2924df029ccSDimitry Andric
293ac9a064cSDimitry Andric size_t NChars = 0;
294ac9a064cSDimitry Andric // The "no-feat" form is allowed in the target attribute but nowhere else.
295ac9a064cSDimitry Andric if (AllowNoDashForm && Modifier.starts_with("no-"))
296ac9a064cSDimitry Andric NChars = 3;
297ac9a064cSDimitry Andric else if (Modifier.starts_with("no"))
298ac9a064cSDimitry Andric NChars = 2;
299ac9a064cSDimitry Andric bool IsNegated = NChars != 0;
300ac9a064cSDimitry Andric StringRef ArchExt = Modifier.drop_front(NChars);
3014df029ccSDimitry Andric
302ac9a064cSDimitry Andric if (auto AE = parseArchExtension(ArchExt)) {
303ac9a064cSDimitry Andric if (AE->PosTargetFeature.empty() || AE->NegTargetFeature.empty())
3044df029ccSDimitry Andric return false;
305ac9a064cSDimitry Andric if (IsNegated)
306ac9a064cSDimitry Andric disable(AE->ID);
307ac9a064cSDimitry Andric else
308ac9a064cSDimitry Andric enable(AE->ID);
309ac9a064cSDimitry Andric return true;
310ac9a064cSDimitry Andric }
311ac9a064cSDimitry Andric return false;
312ac9a064cSDimitry Andric }
313ac9a064cSDimitry Andric
reconstructFromParsedFeatures(const std::vector<std::string> & Features,std::vector<std::string> & NonExtensions)314ac9a064cSDimitry Andric void AArch64::ExtensionSet::reconstructFromParsedFeatures(
315ac9a064cSDimitry Andric const std::vector<std::string> &Features,
316ac9a064cSDimitry Andric std::vector<std::string> &NonExtensions) {
317ac9a064cSDimitry Andric assert(Touched.none() && "Bitset already initialized");
318ac9a064cSDimitry Andric for (auto &F : Features) {
319ac9a064cSDimitry Andric bool IsNegated = F[0] == '-';
320ac9a064cSDimitry Andric if (auto AE = targetFeatureToExtension(F)) {
321ac9a064cSDimitry Andric Touched.set(AE->ID);
322ac9a064cSDimitry Andric if (IsNegated)
323ac9a064cSDimitry Andric Enabled.reset(AE->ID);
324ac9a064cSDimitry Andric else
325ac9a064cSDimitry Andric Enabled.set(AE->ID);
326ac9a064cSDimitry Andric continue;
327ac9a064cSDimitry Andric }
328ac9a064cSDimitry Andric NonExtensions.push_back(F);
329ac9a064cSDimitry Andric }
330ac9a064cSDimitry Andric }
331ac9a064cSDimitry Andric
dump() const332ac9a064cSDimitry Andric void AArch64::ExtensionSet::dump() const {
333ac9a064cSDimitry Andric std::vector<StringRef> Features;
334ac9a064cSDimitry Andric toLLVMFeatureList(Features);
335ac9a064cSDimitry Andric for (StringRef F : Features)
336ac9a064cSDimitry Andric llvm::outs() << F << " ";
337ac9a064cSDimitry Andric llvm::outs() << "\n";
338ac9a064cSDimitry Andric }
339ac9a064cSDimitry Andric
340ac9a064cSDimitry Andric const AArch64::ExtensionInfo &
getExtensionByID(AArch64::ArchExtKind ExtID)341ac9a064cSDimitry Andric AArch64::getExtensionByID(AArch64::ArchExtKind ExtID) {
342ac9a064cSDimitry Andric return lookupExtensionByID(ExtID);
3434df029ccSDimitry Andric }
344