Files
revng-revng/lib/FunctionIsolation/IsolateFunctions.cpp
T
Alessandro Di Federico f68b7866b3 Introduce BasicBlockID
This commit introduces `BasicBlockID` as the unique identifier for a
`efa::BasicBlock` into the CFG. A `BasicBlockID` is defined by a
`MetaAddress` plus an incremental integer. This enables us to have
multiple instances of the same block in a single function, which is
particularly useful when inlining multiple times the same function.

Apart from this, the commit also does the following:

* It drops representing `MetaAddress`es a `structs` in the IR. This created
  several issues related to ABI. We now represent them as strings.

* It defines more functions in `support.h`, instead of defining prototypes
  by hand in `CodeGenerator.cpp` and the like. Specifically, `unknownPC`
  and `raise_exception_helper`. We also introduce a C "constructor" for
  `PlainMetaAddress`.

* It significantly reduces the API of `GeneratedCodeBasicInfo`, which
  was supposed to be put on a diet since a long time.  Specifically,
  many jump target related methods have been moved to free functions in
  `IRHelpers.h`.  Also `GCBI::getSuccessors` has been pushed into its
  only user, `PruneRetSuccessors`, to prevent further usage of a
  deprecated API. In the future, it would be nice to drop it entirely.

* It introduces `efa::BasicBlock::InlinedFrom`.

* Introduce an enum to represent named argument indices for `newpc`.
  This enables us to more effectively manipulate its argument list.

* It improves the verification and error reporting for
  `efa::FunctionMetadata`.

* Update tests.

This commit is preliminary to another piece of work to improve the
generality of inlining beyond the simple "fake function" scenario, for
which the feature was originally conceived.
2023-02-23 14:51:10 +01:00

709 lines
23 KiB
C++

/// \file IsolateFunctions.cpp
/// \brief Implements the IsolateFunctions pass which applies function isolation
/// using the informations provided by EarlyFunctionAnalysis.
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/DepthFirstIterator.h"
#include "llvm/ADT/PostOrderIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Utils/CodeExtractor.h"
#include "llvm/Transforms/Utils/Local.h"
#include "revng/ADT/KeyedObjectContainer.h"
#include "revng/ADT/Queue.h"
#include "revng/ADT/ZipMapIterator.h"
#include "revng/BasicAnalyses/GeneratedCodeBasicInfo.h"
#include "revng/EarlyFunctionAnalysis/BasicBlock.h"
#include "revng/EarlyFunctionAnalysis/CallHandler.h"
#include "revng/EarlyFunctionAnalysis/CollectCFG.h"
#include "revng/EarlyFunctionAnalysis/FunctionEdge.h"
#include "revng/EarlyFunctionAnalysis/FunctionEdgeBase.h"
#include "revng/EarlyFunctionAnalysis/FunctionMetadataCache.h"
#include "revng/EarlyFunctionAnalysis/FunctionSummaryOracle.h"
#include "revng/EarlyFunctionAnalysis/Generated/ForwardDecls.h"
#include "revng/EarlyFunctionAnalysis/Outliner.h"
#include "revng/FunctionIsolation/IsolateFunctions.h"
#include "revng/Model/Binary.h"
#include "revng/Pipeline/AllRegistries.h"
#include "revng/Pipeline/Contract.h"
#include "revng/Pipes/Kinds.h"
#include "revng/Pipes/RootKind.h"
#include "revng/Pipes/TaggedFunctionKind.h"
#include "revng/Support/Debug.h"
#include "revng/Support/FunctionTags.h"
#include "revng/Support/IRHelpers.h"
#include "revng/Support/MetaAddress.h"
using namespace llvm;
class IsolateFunctionsImpl;
static Logger<> TheLogger("isolation");
// Define an alias for the data structure that will contain the LLVM functions
using FunctionsMap = std::map<MDString *, Function *>;
using ValueToValueMap = DenseMap<const Value *, Value *>;
using IF = IsolateFunctions;
using IFI = IsolateFunctionsImpl;
char IF::ID = 0;
static RegisterPass<IF> X("isolate", "Isolate Functions Pass", true, true);
struct IsolatePipe {
static constexpr auto Name = "isolate";
std::vector<pipeline::ContractGroup> getContract() const {
using namespace pipeline;
using namespace ::revng::kinds;
return {
ContractGroup::transformOnlyArgument(Root,
Isolated,
InputPreservation::Preserve)
};
}
void registerPasses(llvm::legacy::PassManager &Manager) {
Manager.add(new efa::CollectCFGPass());
Manager.add(new IsolateFunctions());
}
};
static pipeline::RegisterLLVMPass<IsolatePipe> Y;
class ConstantStringsPool {
private:
Module *M;
std::map<std::string, GlobalVariable *> StringsPool;
public:
ConstantStringsPool(Module *M) : M(M) {}
Constant *get(std::string String, const Twine &Name = "") {
auto It = StringsPool.find(String);
auto &C = M->getContext();
if (It == StringsPool.end()) {
auto *Initializer = ConstantDataArray::getString(C, String, true);
auto *NewVariable = new GlobalVariable(*M,
Initializer->getType(),
true,
GlobalValue::InternalLinkage,
Initializer);
It = StringsPool.insert(It, { String, NewVariable });
}
auto *U8PtrTy = Type::getInt8Ty(C)->getPointerTo();
return ConstantExpr::getPointerCast(It->second, U8PtrTy);
}
};
struct Boundary {
BasicBlock *Block = nullptr;
BasicBlock *CalleeBlock = nullptr;
BasicBlock *ReturnBlock = nullptr;
bool isCall() const { return ReturnBlock != nullptr; }
void dump() const debug_function { dump(dbg); }
template<typename O>
void dump(O &Output) const {
Output << "Block: " << getName(Block) << "\n";
Output << "CalleeBlock: " << getName(CalleeBlock) << "\n";
Output << "ReturnBlock: " << getName(ReturnBlock) << "\n";
}
};
class FunctionBlocks {
private:
enum FixedBlocks {
DummyEntryBlock,
ReturnBlock,
UnexpectedPCBlock,
FixedBlocksCount
};
public:
SmallVector<BasicBlock *, 16> Blocks;
public:
BasicBlock *&dummyEntryBlock() { return Blocks[DummyEntryBlock]; }
BasicBlock *&returnBlock() { return Blocks[ReturnBlock]; }
BasicBlock *&unexpectedPCBlock() { return Blocks[UnexpectedPCBlock]; }
public:
FunctionBlocks() : Blocks(FixedBlocksCount) {}
auto begin() { return Blocks.begin(); }
auto end() { return Blocks.end(); }
void push_back(BasicBlock *BB) { Blocks.push_back(BB); }
bool contains(BasicBlock *BB) const {
return llvm::find(Blocks, BB) != Blocks.end();
}
};
class IsolateFunctionsImpl {
private:
using SuccessorsContainer = std::map<const efa::FunctionEdgeBase *, int>;
private:
Function *RootFunction = nullptr;
Module *TheModule = nullptr;
LLVMContext &Context;
GeneratedCodeBasicInfo &GCBI;
const model::Binary &Binary;
Function *RaiseException = nullptr;
Function *FunctionDispatcher = nullptr;
std::map<MetaAddress, Function *> IsolatedFunctionsMap;
std::map<StringRef, Function *> DynamicFunctionsMap;
ConstantStringsPool Strings;
FunctionMetadataCache *Cache;
public:
IsolateFunctionsImpl(Function *RootFunction,
GeneratedCodeBasicInfo &GCBI,
const model::Binary &Binary,
FunctionMetadataCache &Cache) :
RootFunction(RootFunction),
TheModule(RootFunction->getParent()),
Context(TheModule->getContext()),
GCBI(GCBI),
Binary(Binary),
Strings(TheModule),
Cache(&Cache) {}
public:
Function *getLocalFunction(MetaAddress Entry) const {
return IsolatedFunctionsMap.at(Entry);
}
Function *getDynamicFunction(llvm::StringRef SymbolName) const {
return DynamicFunctionsMap.at(SymbolName);
}
Function *dispatcher() const { return FunctionDispatcher; }
auto &gcbi() const { return GCBI; }
public:
void run();
/// Create code to throw of an exception
void throwException(IRBuilder<> &Builder,
const Twine &Reason,
const DebugLoc &DbgLocation);
void throwException(BasicBlock *BB,
const Twine &Reason,
const DebugLoc &DbgLocation) {
IRBuilder<> Builder(BB);
throwException(Builder, Reason, DbgLocation);
}
private:
/// Populate the function_dispatcher, needed to handle the indirect calls
void populateFunctionDispatcher();
};
void IFI::throwException(IRBuilder<> &Builder,
const Twine &Reason,
const DebugLoc &DbgLocation) {
revng_assert(RaiseException != nullptr);
SmallVector<llvm::Value *, 4> Arguments;
// Create the message string
Arguments.push_back(Strings.get(Reason.str()));
// Populate the source PC
MetaAddress SourcePC = MetaAddress::invalid();
if (Instruction *T = Builder.GetInsertBlock()->getTerminator())
SourcePC = getPC(T).first;
auto *PCH = GCBI.programCounterHandler();
auto AddArguments = [&Arguments, &Builder](llvm::Value *V) {
llvm::copy(unpack(Builder, V), std::back_inserter(Arguments));
};
AddArguments(PCH->buildPlainMetaAddress(Builder, SourcePC));
AddArguments(PCH->buildCurrentPCPlainMetaAddress(Builder));
auto *NewCall = Builder.CreateCall(RaiseException, Arguments);
NewCall->setDebugLoc(DbgLocation);
Builder.CreateUnreachable();
// Assert there's one and only one terminator
auto *BB = Builder.GetInsertBlock();
unsigned Terminators = 0;
for (Instruction &I : *BB)
if (I.isTerminator())
++Terminators;
revng_assert(Terminators == 1);
}
void IFI::populateFunctionDispatcher() {
BasicBlock *Dispatcher = BasicBlock::Create(Context,
"function_dispatcher",
FunctionDispatcher,
nullptr);
BasicBlock *Unexpected = BasicBlock::Create(Context,
"unexpectedpc",
FunctionDispatcher,
nullptr);
const DebugLoc &Dbg = GCBI.unexpectedPC()->getTerminator()->getDebugLoc();
throwException(Unexpected, "An unexpected functions has been called", Dbg);
setBlockType(Unexpected->getTerminator(), BlockType::UnexpectedPCBlock);
IRBuilder<> Builder(Context);
// Create all the entries of the dispatcher
ProgramCounterHandler::DispatcherTargets Targets;
for (auto &[Address, F] : IsolatedFunctionsMap) {
BasicBlock *Trampoline = BasicBlock::Create(Context,
F->getName() + "_trampoline",
FunctionDispatcher,
nullptr);
Targets.emplace_back(Address, Trampoline);
Builder.SetInsertPoint(Trampoline);
Builder.CreateCall(F);
Builder.CreateRetVoid();
}
// Create switch
Builder.SetInsertPoint(Dispatcher);
GCBI.programCounterHandler()->buildDispatcher(Targets,
Builder,
Unexpected,
{});
}
template<typename T, typename F>
static bool
allOrNone(const T &Range, const F &Predicate, bool Default = false) {
auto Start = Range.begin();
auto End = Range.end();
if (Start == End)
return Default;
bool First = Predicate(*Start);
++Start;
for (const auto &E : make_range(Start, End))
revng_assert(First == Predicate(E));
return First;
}
template<typename T, typename F>
static auto
zeroOrOne(const T &Range, const F &Predicate) -> decltype(&*Range.begin()) {
decltype(&*Range.begin()) Result = nullptr;
for (auto &E : Range) {
if (Predicate(E)) {
revng_assert(not Result);
Result = &E;
}
}
return Result;
}
struct SetAtMostOnce {
private:
bool State = false;
public:
bool get() const { return State; }
void set() {
revng_assert(not State);
State = true;
}
void setIf(bool Condition) {
if (Condition)
set();
}
operator bool() const { return State; }
};
template<typename LeftMap, typename RightMap>
void printAddressListComparison(const LeftMap &ExpectedAddresses,
const RightMap &ActualAddresses) {
// Compare expected and actual
if (TheLogger.isEnabled()) {
for (auto [ExpectedAddress, ActualAddress] :
zipmap_range(ExpectedAddresses, ActualAddresses)) {
if (ExpectedAddress == nullptr) {
TheLogger << "Warning: ";
ActualAddress->dump(TheLogger);
TheLogger << " detected as a jump target, but the model does not list "
"it"
<< DoLog;
} else if (ActualAddress == nullptr) {
TheLogger << "Warning: ";
ExpectedAddress->dump(TheLogger);
TheLogger << " not detected as a jump target, but the model lists it"
<< DoLog;
}
}
}
}
class CallIsolatedFunction : public efa::CallHandler {
private:
IsolateFunctionsImpl &IFI;
const efa::FunctionMetadata &FM;
public:
CallIsolatedFunction(IsolateFunctionsImpl &IFI,
const efa::FunctionMetadata &FM) :
IFI(IFI), FM(FM) {}
public:
void handleCall(MetaAddress CallerBlock,
llvm::IRBuilder<> &Builder,
MetaAddress Callee,
const std::set<llvm::GlobalVariable *> &ClobberedRegisters,
const std::optional<int64_t> &MaybeFSO,
bool IsNoReturn,
bool IsTailCall,
llvm::Value *SymbolNamePointer) final {
handleCall(Builder, Callee, SymbolNamePointer);
}
void handlePostNoReturn(llvm::IRBuilder<> &Builder) final {
// TODO: can we do better than DebugLoc()?
IFI.throwException(Builder,
"We return from a noreturn function call",
DebugLoc());
}
void handleIndirectJump(llvm::IRBuilder<> &Builder,
MetaAddress Block,
llvm::Value *SymbolNamePointer) final {
revng_assert(SymbolNamePointer != nullptr);
if (not isa<ConstantPointerNull>(SymbolNamePointer))
handleCall(Builder, MetaAddress::invalid(), SymbolNamePointer);
}
private:
void handleCall(llvm::IRBuilder<> &Builder,
MetaAddress Callee,
llvm::Value *SymbolNamePointer) {
// Identify caller block
const auto *Caller = FM.findBlock(IFI.gcbi(), Builder.GetInsertBlock());
// Identify call edge
auto IsCallEdge = [](const UpcastablePointer<efa::FunctionEdgeBase> &E) {
return isa<efa::CallEdge>(E.get());
};
auto ZeroOrOneCallEdge = [](const auto &Range,
const auto &Predicate) -> efa::CallEdge * {
auto *Result = zeroOrOne(Range, Predicate);
if (Result == nullptr)
return nullptr;
else
return dyn_cast<efa::CallEdge>(Result->get());
};
const auto *CallEdge = ZeroOrOneCallEdge(Caller->Successors(), IsCallEdge);
if (CallEdge == nullptr) {
// There's no CallEdge, this is likely a LongJmp
return;
}
StringRef SymbolName = extractFromConstantStringPtr(SymbolNamePointer);
revng_assert(SymbolName == CallEdge->DynamicFunction());
revng_assert(Callee == CallEdge->Destination().notInlinedAddress());
// Identify callee
Function *CalledFunction = nullptr;
if (Callee.isValid())
CalledFunction = IFI.getLocalFunction(Callee);
else if (not SymbolName.empty())
CalledFunction = IFI.getDynamicFunction(SymbolName);
else
CalledFunction = IFI.dispatcher();
//
// Create the call
//
BasicBlock::iterator InsertPoint = Builder.GetInsertPoint();
revng_assert(not Builder.GetInsertBlock()->empty());
bool AtEnd = InsertPoint == Builder.GetInsertBlock()->end();
Instruction *Old = AtEnd ? &*Builder.GetInsertBlock()->rbegin() :
&*InsertPoint;
auto *NewCall = Builder.CreateCall(CalledFunction);
NewCall->setDebugLoc(Old->getDebugLoc());
FunctionTags::CallToLifted.addTo(NewCall);
setStringMetadata(NewCall, CallerBlockStartMDName, Caller->ID().toString());
}
};
template<typename R>
inline auto toVector(R &&Range) {
using ResultType = decltype(*Range.begin());
SmallVector<ResultType> Result;
for (auto Element : Range)
Result.push_back(Element);
return Result;
}
class FunctionOutliner {
private:
GeneratedCodeBasicInfo &GCBI;
efa::FunctionSummaryOracle Oracle;
efa::Outliner Outliner;
public:
FunctionOutliner(llvm::Module &M,
const model::Binary &Binary,
GeneratedCodeBasicInfo &GCBI) :
GCBI(GCBI), Outliner(M, GCBI, Oracle) {
importModel(M, GCBI, Binary, Oracle);
}
public:
efa::OutlinedFunction
outline(MetaAddress Entry, efa::CallHandler *TheCallHandler) {
return Outliner.outline(GCBI.getBlockAt(Entry), TheCallHandler);
}
};
void IsolateFunctionsImpl::run() {
RaiseException = TheModule->getFunction("raise_exception_helper");
revng_assert(RaiseException != nullptr);
FunctionTags::Exceptional.addTo(RaiseException);
FunctionDispatcher = Function::Create(createFunctionType<void>(Context),
GlobalValue::ExternalLinkage,
"function_dispatcher",
TheModule);
FunctionTags::FunctionDispatcher.addTo(FunctionDispatcher);
auto *IsolatedFunctionType = createFunctionType<void>(Context);
//
// Create the dynamic functions
//
for (const model::DynamicFunction &Function :
Binary.ImportedDynamicFunctions()) {
StringRef Name = Function.OriginalName();
auto *NewFunction = Function::Create(IsolatedFunctionType,
GlobalValue::ExternalLinkage,
"dynamic_" + Function.OriginalName(),
TheModule);
FunctionTags::DynamicFunction.addTo(NewFunction);
auto *EntryBB = BasicBlock::Create(Context, "", NewFunction);
throwException(EntryBB, Twine("Dynamic call ") + Name, DebugLoc());
// TODO: implement more efficient version.
// if (setjmp(...) == 0) {
// // First return
// serialize_cpu_state();
// dynamic_function();
// // If we get here, it means that the external function return properly
// deserialize_cpu_state();
// simulate_ret();
// // If the caller tail-called us, it must return immediately, without
// // checking if the pc is the fallthrough of the call (which was not a
// // call!)
// } else {
// // If we get here, it means that the external function either invoked a
// // callback or something else weird i going on.
// deserialize_cpu_state();
// throw_exception();
// }
DynamicFunctionsMap[Name] = NewFunction;
}
//
// Precreate the isolated functions
//
for (const model::Function &Function : Binary.Functions()) {
auto *NewFunction = Function::Create(IsolatedFunctionType,
GlobalValue::ExternalLinkage,
"local_" + Function.name(),
TheModule);
NewFunction->addFnAttr(Attribute::NullPointerIsValid);
IsolatedFunctionsMap[Function.Entry()] = NewFunction;
FunctionTags::Isolated.addTo(NewFunction);
revng_assert(NewFunction != nullptr);
setMetaAddressMetadata(NewFunction, FunctionEntryMDNName, Function.Entry());
auto *OriginalEntry = GCBI.getBlockAt(Function.Entry())->getTerminator();
auto *MDNode = OriginalEntry->getMetadata(FunctionMetadataMDName);
NewFunction->setMetadata(FunctionMetadataMDName, MDNode);
}
using namespace efa;
using llvm::BasicBlock;
FunctionOutliner Outliner(*TheModule, Binary, GCBI);
for (auto &[Entry, F] : IsolatedFunctionsMap) {
BasicBlock *OriginalEntryBlock = GCBI.getBlockAt(Entry);
const auto &FM = Cache->getFunctionMetadata(OriginalEntryBlock);
CallIsolatedFunction CallHandler(*this, FM);
OutlinedFunction Outlined = Outliner.outline(Entry, &CallHandler);
//
// Handle UnexpectedPCCloned
//
if (BasicBlock *UnexpectedPC = Outlined.UnexpectedPCCloned) {
UnexpectedPC->getInstList().clear();
revng_assert(UnexpectedPC->empty());
const DebugLoc &Dbg = GCBI.unexpectedPC()->getTerminator()->getDebugLoc();
throwException(UnexpectedPC, "unexpectedPC", Dbg);
}
//
// Handle jumps to AnyPC
//
if (BasicBlock *AnyPC = Outlined.AnyPCCloned) {
for (BasicBlock *AnyPCPredecessor : toVector(predecessors(AnyPC))) {
// First of all, identify the basic block
const efa::BasicBlock *Block = FM.findBlock(GCBI, AnyPCPredecessor);
Instruction *T = AnyPCPredecessor->getTerminator();
revng_assert(not cast<BranchInst>(T)->isConditional());
T->eraseFromParent();
IRBuilder<> Builder(AnyPCPredecessor);
// Get the only outgoing edge jumping to anypc
if (Block == nullptr) {
throwException(Builder, "Unexpected jump", DebugLoc());
continue;
}
bool AtLeastAMatch = false;
for (auto &Edge : Block->Successors()) {
if (Edge->Type() == efa::FunctionEdgeType::DirectBranch)
continue;
revng_assert(not AtLeastAMatch);
AtLeastAMatch = true;
switch (Edge->Type()) {
case efa::FunctionEdgeType::Return:
Builder.CreateRetVoid();
break;
case efa::FunctionEdgeType::BrokenReturn:
// TODO: can we do better than DebugLoc()?
throwException(Builder, "A broken return was taken", DebugLoc());
break;
case efa::FunctionEdgeType::LongJmp:
throwException(Builder, "A longjmp was taken", DebugLoc());
break;
case efa::FunctionEdgeType::Killer:
throwException(Builder,
"A killer block has been reached",
DebugLoc());
revng_abort();
break;
case efa::FunctionEdgeType::Unreachable:
throwException(Builder,
"An unreachable instruction has been "
"reached",
DebugLoc());
break;
case efa::FunctionEdgeType::FunctionCall: {
auto *Call = cast<efa::CallEdge>(Edge.get());
revng_assert(Call->IsTailCall());
Builder.CreateRetVoid();
} break;
case efa::FunctionEdgeType::Invalid:
case efa::FunctionEdgeType::DirectBranch:
case efa::FunctionEdgeType::Count:
revng_abort();
break;
}
}
if (not AtLeastAMatch) {
throwException(Builder, "Unexpected jump", DebugLoc());
continue;
}
}
eraseFromParent(AnyPC);
}
if (Outlined.Function)
for (BasicBlock &BB : *Outlined.Function)
revng_assert(BB.getTerminator() != nullptr);
// Steal the function body and let the outlined function be destroyed
moveBlocksInto(*Outlined.Function, *F);
}
revng_check(not verifyModule(*TheModule, &dbgs()));
// Create the functions and basic blocks needed for the correct execution of
// the exception handling mechanism
// Populate the function_dispatcher
populateFunctionDispatcher();
// Cleanup root
EliminateUnreachableBlocks(*RootFunction, nullptr, false);
// Before emitting it in output we check that the module in passes the
// verifyModule pass
if (VerifyLog.isEnabled())
revng_assert(not verifyModule(*TheModule, &dbgs()));
FunctionTags::IsolatedRoot.addTo(RootFunction);
}
bool IF::runOnModule(Module &TheModule) {
if (not TheModule.getFunction("root")
or TheModule.getFunction("root")->isDeclaration())
return false;
// Retrieve analyses
auto &GCBI = getAnalysis<GeneratedCodeBasicInfoWrapperPass>().getGCBI();
const auto &ModelWrapper = getAnalysis<LoadModelWrapperPass>().get();
const model::Binary &Binary = *ModelWrapper.getReadOnlyModel();
// Create an object of type IsolateFunctionsImpl and run the pass
IFI Impl(TheModule.getFunction("root"),
GCBI,
Binary,
getAnalysis<FunctionMetadataCachePass>().get());
Impl.run();
return false;
}
void IsolateFunctions::getAnalysisUsage(llvm::AnalysisUsage &AU) const {
AU.setPreservesAll();
AU.addRequired<GeneratedCodeBasicInfoWrapperPass>();
AU.addRequired<LoadModelWrapperPass>();
AU.addRequired<FunctionMetadataCachePass>();
}