123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- ///////////////////////////////////////////////////////////////////////////////
- // //
- // HLOperations.cpp //
- // Copyright (C) Microsoft Corporation. All rights reserved. //
- // This file is distributed under the University of Illinois Open Source //
- // License. See LICENSE.TXT for details. //
- // //
- // Implementation of DXIL operations. //
- // //
- ///////////////////////////////////////////////////////////////////////////////
- #include "dxc/HLSL/HLOperations.h"
- #include "dxc/HlslIntrinsicOp.h"
- #include "llvm/IR/Function.h"
- #include "llvm/IR/Instructions.h"
- #include "llvm/IR/Module.h"
- #include "llvm/Support/raw_ostream.h"
- using namespace hlsl;
- using namespace llvm;
- namespace hlsl {
- const char HLPrefixStr [] = "dx.hl";
- const char * const HLPrefix = HLPrefixStr;
- static const char HLLowerStrategyStr[] = "dx.hlls";
- static const char * const HLLowerStrategy = HLLowerStrategyStr;
- static const char HLWaveSensitiveStr[] = "dx.wave-sensitive";
- static const char * const HLWaveSensitive = HLWaveSensitiveStr;
- static StringRef HLOpcodeGroupNames[]{
- "notHLDXIL", // NotHL,
- "<ext>", // HLExtIntrinsic - should always refer through extension
- "op", // HLIntrinsic,
- "cast", // HLCast,
- "init", // HLInit,
- "binop", // HLBinOp,
- "unop", // HLUnOp,
- "subscript", // HLSubscript,
- "matldst", // HLMatLoadStore,
- "select", // HLSelect,
- "createhandle",// HLCreateHandle,
- "annotatehandle" // HLAnnotateHandle,
- "numOfHLDXIL", // NumOfHLOps
- };
- static StringRef HLOpcodeGroupFullNames[]{
- "notHLDXIL", // NotHL,
- "<ext>", // HLExtIntrinsic - should aways refer through extension
- "dx.hl.op", // HLIntrinsic,
- "dx.hl.cast", // HLCast,
- "dx.hl.init", // HLInit,
- "dx.hl.binop", // HLBinOp,
- "dx.hl.unop", // HLUnOp,
- "dx.hl.subscript", // HLSubscript,
- "dx.hl.matldst", // HLMatLoadStore,
- "dx.hl.select", // HLSelect,
- "dx.hl.createhandle", // HLCreateHandle,
- "dx.hl.annotatehandle", // HLAnnotateHandle,
- "numOfHLDXIL", // NumOfHLOps
- };
- static HLOpcodeGroup GetHLOpcodeGroupInternal(StringRef group) {
- if (!group.empty()) {
- switch (group[0]) {
- case 'o': // op
- return HLOpcodeGroup::HLIntrinsic;
- case 'c': // cast
- switch (group[1]) {
- case 'a': // cast
- return HLOpcodeGroup::HLCast;
- case 'r': // createhandle
- return HLOpcodeGroup::HLCreateHandle;
- }
- case 'i': // init
- return HLOpcodeGroup::HLInit;
- case 'b': // binaryOp
- return HLOpcodeGroup::HLBinOp;
- case 'u': // unaryOp
- return HLOpcodeGroup::HLUnOp;
- case 's': // subscript
- switch (group[1]) {
- case 'u':
- return HLOpcodeGroup::HLSubscript;
- case 'e':
- return HLOpcodeGroup::HLSelect;
- }
- case 'm': // matldst
- return HLOpcodeGroup::HLMatLoadStore;
- case 'a': // annotatehandle
- return HLOpcodeGroup::HLAnnotateHandle;
- }
- }
- return HLOpcodeGroup::NotHL;
- }
- // GetHLOpGroup by function name.
- HLOpcodeGroup GetHLOpcodeGroupByName(const Function *F) {
- StringRef name = F->getName();
- if (!name.startswith(HLPrefix)) {
- // This could be an external intrinsic, but this function
- // won't recognize those as such. Use GetHLOpcodeGroupByName
- // to make that distinction.
- return HLOpcodeGroup::NotHL;
- }
- const unsigned prefixSize = sizeof(HLPrefixStr);
- StringRef group = name.substr(prefixSize);
- return GetHLOpcodeGroupInternal(group);
- }
- HLOpcodeGroup GetHLOpcodeGroup(llvm::Function *F) {
- llvm::StringRef name = GetHLOpcodeGroupNameByAttr(F);
- HLOpcodeGroup result = GetHLOpcodeGroupInternal(name);
- if (result == HLOpcodeGroup::NotHL) {
- result = name.empty() ? result : HLOpcodeGroup::HLExtIntrinsic;
- }
- if (result == HLOpcodeGroup::NotHL) {
- result = GetHLOpcodeGroupByName(F);
- }
- return result;
- }
- llvm::StringRef GetHLOpcodeGroupNameByAttr(llvm::Function *F) {
- Attribute groupAttr = F->getFnAttribute(hlsl::HLPrefix);
- StringRef group = groupAttr.getValueAsString();
- return group;
- }
- StringRef GetHLOpcodeGroupName(HLOpcodeGroup op) {
- switch (op) {
- case HLOpcodeGroup::HLCast:
- case HLOpcodeGroup::HLInit:
- case HLOpcodeGroup::HLBinOp:
- case HLOpcodeGroup::HLUnOp:
- case HLOpcodeGroup::HLIntrinsic:
- case HLOpcodeGroup::HLSubscript:
- case HLOpcodeGroup::HLMatLoadStore:
- case HLOpcodeGroup::HLSelect:
- case HLOpcodeGroup::HLCreateHandle:
- case HLOpcodeGroup::HLAnnotateHandle:
- return HLOpcodeGroupNames[static_cast<unsigned>(op)];
- default:
- llvm_unreachable("invalid op");
-
- return "";
- }
- }
- StringRef GetHLOpcodeGroupFullName(HLOpcodeGroup op) {
- switch (op) {
- case HLOpcodeGroup::HLCast:
- case HLOpcodeGroup::HLInit:
- case HLOpcodeGroup::HLBinOp:
- case HLOpcodeGroup::HLUnOp:
- case HLOpcodeGroup::HLIntrinsic:
- case HLOpcodeGroup::HLSubscript:
- case HLOpcodeGroup::HLMatLoadStore:
- case HLOpcodeGroup::HLSelect:
- case HLOpcodeGroup::HLCreateHandle:
- case HLOpcodeGroup::HLAnnotateHandle:
- return HLOpcodeGroupFullNames[static_cast<unsigned>(op)];
- default:
- llvm_unreachable("invalid op");
- return "";
- }
- }
- llvm::StringRef GetHLOpcodeName(HLUnaryOpcode Op) {
- switch (Op) {
- case HLUnaryOpcode::PostInc: return "++";
- case HLUnaryOpcode::PostDec: return "--";
- case HLUnaryOpcode::PreInc: return "++";
- case HLUnaryOpcode::PreDec: return "--";
- case HLUnaryOpcode::Plus: return "+";
- case HLUnaryOpcode::Minus: return "-";
- case HLUnaryOpcode::Not: return "~";
- case HLUnaryOpcode::LNot: return "!";
- case HLUnaryOpcode::Invalid:
- case HLUnaryOpcode::NumOfUO:
- // Invalid Unary Ops
- break;
- }
- llvm_unreachable("Unknown unary operator");
- }
- llvm::StringRef GetHLOpcodeName(HLBinaryOpcode Op) {
- switch (Op) {
- case HLBinaryOpcode::Mul: return "*";
- case HLBinaryOpcode::UDiv:
- case HLBinaryOpcode::Div: return "/";
- case HLBinaryOpcode::URem:
- case HLBinaryOpcode::Rem: return "%";
- case HLBinaryOpcode::Add: return "+";
- case HLBinaryOpcode::Sub: return "-";
- case HLBinaryOpcode::Shl: return "<<";
- case HLBinaryOpcode::UShr:
- case HLBinaryOpcode::Shr: return ">>";
- case HLBinaryOpcode::ULT:
- case HLBinaryOpcode::LT: return "<";
- case HLBinaryOpcode::UGT:
- case HLBinaryOpcode::GT: return ">";
- case HLBinaryOpcode::ULE:
- case HLBinaryOpcode::LE: return "<=";
- case HLBinaryOpcode::UGE:
- case HLBinaryOpcode::GE: return ">=";
- case HLBinaryOpcode::EQ: return "==";
- case HLBinaryOpcode::NE: return "!=";
- case HLBinaryOpcode::And: return "&";
- case HLBinaryOpcode::Xor: return "^";
- case HLBinaryOpcode::Or: return "|";
- case HLBinaryOpcode::LAnd: return "&&";
- case HLBinaryOpcode::LOr: return "||";
- case HLBinaryOpcode::Invalid:
- case HLBinaryOpcode::NumOfBO:
- // Invalid Binary Ops
- break;
- }
- llvm_unreachable("Invalid OpCode!");
- }
- llvm::StringRef GetHLOpcodeName(HLSubscriptOpcode Op) {
- switch (Op) {
- case HLSubscriptOpcode::DefaultSubscript:
- return "[]";
- case HLSubscriptOpcode::ColMatSubscript:
- return "colMajor[]";
- case HLSubscriptOpcode::RowMatSubscript:
- return "rowMajor[]";
- case HLSubscriptOpcode::ColMatElement:
- return "colMajor_m";
- case HLSubscriptOpcode::RowMatElement:
- return "rowMajor_m";
- case HLSubscriptOpcode::DoubleSubscript:
- return "[][]";
- case HLSubscriptOpcode::CBufferSubscript:
- return "cb";
- case HLSubscriptOpcode::VectorSubscript:
- return "vector[]";
- }
- return "";
- }
- llvm::StringRef GetHLOpcodeName(HLCastOpcode Op) {
- switch (Op) {
- case HLCastOpcode::DefaultCast:
- return "default";
- case HLCastOpcode::ToUnsignedCast:
- return "toUnsigned";
- case HLCastOpcode::FromUnsignedCast:
- return "fromUnsigned";
- case HLCastOpcode::UnsignedUnsignedCast:
- return "unsignedUnsigned";
- case HLCastOpcode::ColMatrixToVecCast:
- return "colMatToVec";
- case HLCastOpcode::RowMatrixToVecCast:
- return "rowMatToVec";
- case HLCastOpcode::ColMatrixToRowMatrix:
- return "colMatToRowMat";
- case HLCastOpcode::RowMatrixToColMatrix:
- return "rowMatToColMat";
- case HLCastOpcode::HandleToResCast:
- return "handleToRes";
- }
- return "";
- }
- llvm::StringRef GetHLOpcodeName(HLMatLoadStoreOpcode Op) {
- switch (Op) {
- case HLMatLoadStoreOpcode::ColMatLoad:
- return "colLoad";
- case HLMatLoadStoreOpcode::ColMatStore:
- return "colStore";
- case HLMatLoadStoreOpcode::RowMatLoad:
- return "rowLoad";
- case HLMatLoadStoreOpcode::RowMatStore:
- return "rowStore";
- }
- llvm_unreachable("invalid matrix load store operator");
- }
- StringRef GetHLLowerStrategy(Function *F) {
- llvm::Attribute A = F->getFnAttribute(HLLowerStrategy);
- llvm::StringRef LowerStrategy = A.getValueAsString();
- return LowerStrategy;
- }
- void SetHLLowerStrategy(Function *F, StringRef S) {
- F->addFnAttr(HLLowerStrategy, S);
- }
- // Set function attribute indicating wave-sensitivity
- void SetHLWaveSensitive(Function *F) {
- F->addFnAttr(HLWaveSensitive, "y");
- }
- // Return if this Function is dependent on other wave members indicated by attribute
- bool IsHLWaveSensitive(Function *F) {
- AttributeSet attrSet = F->getAttributes();
- return attrSet.hasAttribute(AttributeSet::FunctionIndex, HLWaveSensitive);
- }
- std::string GetHLFullName(HLOpcodeGroup op, unsigned opcode) {
- assert(op != HLOpcodeGroup::HLExtIntrinsic && "else table name should be used");
- std::string opName = GetHLOpcodeGroupFullName(op).str() + ".";
- switch (op) {
- case HLOpcodeGroup::HLBinOp: {
- HLBinaryOpcode binOp = static_cast<HLBinaryOpcode>(opcode);
- return opName + GetHLOpcodeName(binOp).str();
- }
- case HLOpcodeGroup::HLUnOp: {
- HLUnaryOpcode unOp = static_cast<HLUnaryOpcode>(opcode);
- return opName + GetHLOpcodeName(unOp).str();
- }
- case HLOpcodeGroup::HLIntrinsic: {
- // intrinsic with same signature will share the funciton now
- // The opcode is in arg0.
- return opName;
- }
- case HLOpcodeGroup::HLMatLoadStore: {
- HLMatLoadStoreOpcode matOp = static_cast<HLMatLoadStoreOpcode>(opcode);
- return opName + GetHLOpcodeName(matOp).str();
- }
- case HLOpcodeGroup::HLSubscript: {
- HLSubscriptOpcode subOp = static_cast<HLSubscriptOpcode>(opcode);
- return opName + GetHLOpcodeName(subOp).str();
- }
- case HLOpcodeGroup::HLCast: {
- HLCastOpcode castOp = static_cast<HLCastOpcode>(opcode);
- return opName + GetHLOpcodeName(castOp).str();
- }
- default:
- return opName;
- }
- }
- // Get opcode from arg0 of function call.
- unsigned GetHLOpcode(const CallInst *CI) {
- Value *idArg = CI->getArgOperand(HLOperandIndex::kOpcodeIdx);
- Constant *idConst = cast<Constant>(idArg);
- return idConst->getUniqueInteger().getLimitedValue();
- }
- unsigned GetRowMajorOpcode(HLOpcodeGroup group, unsigned opcode) {
- switch (group) {
- case HLOpcodeGroup::HLMatLoadStore: {
- HLMatLoadStoreOpcode matOp = static_cast<HLMatLoadStoreOpcode>(opcode);
- switch (matOp) {
- case HLMatLoadStoreOpcode::ColMatLoad:
- return static_cast<unsigned>(HLMatLoadStoreOpcode::RowMatLoad);
- case HLMatLoadStoreOpcode::ColMatStore:
- return static_cast<unsigned>(HLMatLoadStoreOpcode::RowMatStore);
- default:
- return opcode;
- }
- } break;
- case HLOpcodeGroup::HLSubscript: {
- HLSubscriptOpcode subOp = static_cast<HLSubscriptOpcode>(opcode);
- switch (subOp) {
- case HLSubscriptOpcode::ColMatElement:
- return static_cast<unsigned>(HLSubscriptOpcode::RowMatElement);
- case HLSubscriptOpcode::ColMatSubscript:
- return static_cast<unsigned>(HLSubscriptOpcode::RowMatSubscript);
- default:
- return opcode;
- }
- } break;
- default:
- return opcode;
- }
- }
- bool HasUnsignedOpcode(unsigned opcode) {
- return HasUnsignedIntrinsicOpcode(static_cast<IntrinsicOp>(opcode));
- }
- unsigned GetUnsignedOpcode(unsigned opcode) {
- return GetUnsignedIntrinsicOpcode(static_cast<IntrinsicOp>(opcode));
- }
- // For HLBinaryOpcode
- bool HasUnsignedOpcode(HLBinaryOpcode opcode) {
- switch (opcode) {
- case HLBinaryOpcode::Div:
- case HLBinaryOpcode::Rem:
- case HLBinaryOpcode::Shr:
- case HLBinaryOpcode::LT:
- case HLBinaryOpcode::GT:
- case HLBinaryOpcode::LE:
- case HLBinaryOpcode::GE:
- return true;
- default:
- return false;
- }
- }
- HLBinaryOpcode GetUnsignedOpcode(HLBinaryOpcode opcode) {
- switch (opcode) {
- case HLBinaryOpcode::Div:
- return HLBinaryOpcode::UDiv;
- case HLBinaryOpcode::Rem:
- return HLBinaryOpcode::URem;
- case HLBinaryOpcode::Shr:
- return HLBinaryOpcode::UShr;
- case HLBinaryOpcode::LT:
- return HLBinaryOpcode::ULT;
- case HLBinaryOpcode::GT:
- return HLBinaryOpcode::UGT;
- case HLBinaryOpcode::LE:
- return HLBinaryOpcode::ULE;
- case HLBinaryOpcode::GE:
- return HLBinaryOpcode::UGE;
- default:
- return opcode;
- }
- }
- static void SetHLFunctionAttribute(Function *F, HLOpcodeGroup group,
- unsigned opcode) {
- switch (group) {
- case HLOpcodeGroup::HLUnOp:
- case HLOpcodeGroup::HLBinOp:
- case HLOpcodeGroup::HLCast:
- case HLOpcodeGroup::HLSubscript:
- if (!F->hasFnAttribute(Attribute::ReadNone)) {
- F->addFnAttr(Attribute::ReadNone);
- F->addFnAttr(Attribute::NoUnwind);
- }
- break;
- case HLOpcodeGroup::HLInit:
- if (!F->hasFnAttribute(Attribute::ReadNone))
- if (!F->getReturnType()->isVoidTy()) {
- F->addFnAttr(Attribute::ReadNone);
- F->addFnAttr(Attribute::NoUnwind);
- }
- break;
- case HLOpcodeGroup::HLMatLoadStore: {
- HLMatLoadStoreOpcode matOp = static_cast<HLMatLoadStoreOpcode>(opcode);
- if (matOp == HLMatLoadStoreOpcode::ColMatLoad ||
- matOp == HLMatLoadStoreOpcode::RowMatLoad)
- if (!F->hasFnAttribute(Attribute::ReadOnly)) {
- F->addFnAttr(Attribute::ReadOnly);
- F->addFnAttr(Attribute::NoUnwind);
- }
- } break;
- case HLOpcodeGroup::HLCreateHandle: {
- F->addFnAttr(Attribute::ReadNone);
- F->addFnAttr(Attribute::NoUnwind);
- } break;
- case HLOpcodeGroup::HLAnnotateHandle: {
- F->addFnAttr(Attribute::ReadNone);
- F->addFnAttr(Attribute::NoUnwind);
- } break;
- case HLOpcodeGroup::HLIntrinsic: {
- IntrinsicOp intrinsicOp = static_cast<IntrinsicOp>(opcode);
- switch (intrinsicOp) {
- default:
- break;
- case IntrinsicOp::IOP_DeviceMemoryBarrierWithGroupSync:
- case IntrinsicOp::IOP_DeviceMemoryBarrier:
- case IntrinsicOp::IOP_GroupMemoryBarrierWithGroupSync:
- case IntrinsicOp::IOP_GroupMemoryBarrier:
- case IntrinsicOp::IOP_AllMemoryBarrierWithGroupSync:
- case IntrinsicOp::IOP_AllMemoryBarrier:
- F->addFnAttr(Attribute::NoDuplicate);
- break;
- }
- } break;
- case HLOpcodeGroup::NotHL:
- case HLOpcodeGroup::HLExtIntrinsic:
- case HLOpcodeGroup::HLSelect:
- case HLOpcodeGroup::NumOfHLOps:
- // No default attributes for these opcodes.
- break;
- }
- }
- Function *GetOrCreateHLFunction(Module &M, FunctionType *funcTy,
- HLOpcodeGroup group, unsigned opcode) {
- AttributeSet attribs;
- return GetOrCreateHLFunction(M, funcTy, group, nullptr, nullptr, opcode, attribs);
- }
- Function *GetOrCreateHLFunction(Module &M, FunctionType *funcTy,
- HLOpcodeGroup group, StringRef *groupName,
- StringRef *fnName, unsigned opcode) {
- AttributeSet attribs;
- return GetOrCreateHLFunction(M, funcTy, group, groupName, fnName, opcode, attribs);
- }
- Function *GetOrCreateHLFunction(Module &M, FunctionType *funcTy,
- HLOpcodeGroup group, unsigned opcode,
- const AttributeSet &attribs) {
- return GetOrCreateHLFunction(M, funcTy, group, nullptr, nullptr, opcode, attribs);
- }
- Function *GetOrCreateHLFunction(Module &M, FunctionType *funcTy,
- HLOpcodeGroup group, StringRef *groupName,
- StringRef *fnName, unsigned opcode,
- const AttributeSet &attribs) {
- std::string mangledName;
- raw_string_ostream mangledNameStr(mangledName);
- if (group == HLOpcodeGroup::HLExtIntrinsic) {
- assert(groupName && "else intrinsic should have been rejected");
- assert(fnName && "else intrinsic should have been rejected");
- mangledNameStr << *groupName;
- mangledNameStr << '.';
- mangledNameStr << *fnName;
- }
- else {
- mangledNameStr << GetHLFullName(group, opcode);
- // Need to add wave sensitivity to name to prevent clashes with non-wave intrinsic
- if(attribs.hasAttribute(AttributeSet::FunctionIndex, HLWaveSensitive))
- mangledNameStr << "wave";
- mangledNameStr << '.';
- funcTy->print(mangledNameStr);
- }
- mangledNameStr.flush();
- Function *F = cast<Function>(M.getOrInsertFunction(mangledName, funcTy));
- if (group == HLOpcodeGroup::HLExtIntrinsic) {
- F->addFnAttr(hlsl::HLPrefix, *groupName);
- }
- SetHLFunctionAttribute(F, group, opcode);
- // Copy attributes
- if (attribs.hasAttribute(AttributeSet::FunctionIndex, Attribute::ReadNone))
- F->addFnAttr(Attribute::ReadNone);
- if (attribs.hasAttribute(AttributeSet::FunctionIndex, Attribute::ReadOnly))
- F->addFnAttr(Attribute::ReadOnly);
- if (attribs.hasAttribute(AttributeSet::FunctionIndex, HLWaveSensitive))
- F->addFnAttr(HLWaveSensitive, "y");
- return F;
- }
- // HLFunction with body cannot share with HLFunction without body.
- // So need add name.
- Function *GetOrCreateHLFunctionWithBody(Module &M, FunctionType *funcTy,
- HLOpcodeGroup group, unsigned opcode,
- StringRef name) {
- std::string operatorName = GetHLFullName(group, opcode);
- std::string mangledName = operatorName + "." + name.str();
- raw_string_ostream mangledNameStr(mangledName);
- funcTy->print(mangledNameStr);
- mangledNameStr.flush();
- Function *F = cast<Function>(M.getOrInsertFunction(mangledName, funcTy));
- SetHLFunctionAttribute(F, group, opcode);
- F->setLinkage(llvm::GlobalValue::LinkageTypes::InternalLinkage);
- return F;
- }
- Value *callHLFunction(Module &Module, HLOpcodeGroup OpcodeGroup, unsigned Opcode,
- Type *RetTy, ArrayRef<Value*> Args, IRBuilder<> &Builder) {
- AttributeSet attribs;
- return callHLFunction(Module, OpcodeGroup, Opcode, RetTy, Args, attribs, Builder);
- }
- Value *callHLFunction(Module &Module, HLOpcodeGroup OpcodeGroup, unsigned Opcode,
- Type *RetTy, ArrayRef<Value*> Args, const AttributeSet &attribs, IRBuilder<> &Builder) {
- SmallVector<Type*, 4> ArgTys;
- ArgTys.reserve(Args.size());
- for (Value *Arg : Args)
- ArgTys.emplace_back(Arg->getType());
- FunctionType *FuncTy = FunctionType::get(RetTy, ArgTys, /* isVarArg */ false);
- Function *Func = GetOrCreateHLFunction(Module, FuncTy, OpcodeGroup, Opcode, attribs);
- return Builder.CreateCall(Func, Args);
- }
- } // namespace hlsl
|