Mercurial > projects > ldc
comparison gen/statements.cpp @ 213:7816aafeea3c trunk
[svn r229] Updated the object.d implementation to the latest Tango.
Fixed a bunch of the built-in typeinfos for arrays, they did not inherit TypeInfo_Array.
Applied patch to tango/text/convert/Layout.d by fvbommel, closes #47 .
Cleaned up some type code.
Replaced uses of llvm::Type with LLType (a typedef), same for Value and Constant.
Fixed a few cases where typeinfo for user structs could be emitted multiple times, seems to still be some cases of this :/
author | lindquist |
---|---|
date | Fri, 30 May 2008 19:32:04 +0200 |
parents | cd2c9f4010e4 |
children | 0806379a5eca |
comparison
equal
deleted
inserted
replaced
212:4c2689d57ba4 | 213:7816aafeea3c |
---|---|
102 | 102 |
103 } | 103 } |
104 else { | 104 else { |
105 if (global.params.symdebug) DtoDwarfStopPoint(loc.linnum); | 105 if (global.params.symdebug) DtoDwarfStopPoint(loc.linnum); |
106 DValue* e = exp->toElem(p); | 106 DValue* e = exp->toElem(p); |
107 llvm::Value* v = e->getRVal(); | 107 LLValue* v = e->getRVal(); |
108 delete e; | 108 delete e; |
109 Logger::cout() << "return value is '" <<*v << "'\n"; | 109 Logger::cout() << "return value is '" <<*v << "'\n"; |
110 | 110 |
111 // can happen for classes | 111 // can happen for classes |
112 if (v->getType() != p->topfunc()->getReturnType()) | 112 if (v->getType() != p->topfunc()->getReturnType()) |
175 Logger::println("IfStatement::toIR(): %s", loc.toChars()); | 175 Logger::println("IfStatement::toIR(): %s", loc.toChars()); |
176 LOG_SCOPE; | 176 LOG_SCOPE; |
177 | 177 |
178 if (match) | 178 if (match) |
179 { | 179 { |
180 llvm::Value* allocainst = new llvm::AllocaInst(DtoType(match->type), "._tmp_if_var", p->topallocapoint()); | 180 LLValue* allocainst = new llvm::AllocaInst(DtoType(match->type), "._tmp_if_var", p->topallocapoint()); |
181 match->ir.irLocal = new IrLocal(match); | 181 match->ir.irLocal = new IrLocal(match); |
182 match->ir.irLocal->value = allocainst; | 182 match->ir.irLocal->value = allocainst; |
183 } | 183 } |
184 | 184 |
185 DValue* cond_e = condition->toElem(p); | 185 DValue* cond_e = condition->toElem(p); |
186 llvm::Value* cond_val = cond_e->getRVal(); | 186 LLValue* cond_val = cond_e->getRVal(); |
187 delete cond_e; | 187 delete cond_e; |
188 | 188 |
189 llvm::BasicBlock* oldend = gIR->scopeend(); | 189 llvm::BasicBlock* oldend = gIR->scopeend(); |
190 | 190 |
191 llvm::BasicBlock* ifbb = llvm::BasicBlock::Create("if", gIR->topfunc(), oldend); | 191 llvm::BasicBlock* ifbb = llvm::BasicBlock::Create("if", gIR->topfunc(), oldend); |
194 | 194 |
195 if (cond_val->getType() != llvm::Type::Int1Ty) { | 195 if (cond_val->getType() != llvm::Type::Int1Ty) { |
196 Logger::cout() << "if conditional: " << *cond_val << '\n'; | 196 Logger::cout() << "if conditional: " << *cond_val << '\n'; |
197 cond_val = DtoBoolean(cond_val); | 197 cond_val = DtoBoolean(cond_val); |
198 } | 198 } |
199 llvm::Value* ifgoback = llvm::BranchInst::Create(ifbb, elsebb, cond_val, gIR->scopebb()); | 199 LLValue* ifgoback = llvm::BranchInst::Create(ifbb, elsebb, cond_val, gIR->scopebb()); |
200 | 200 |
201 // replace current scope | 201 // replace current scope |
202 gIR->scope() = IRScope(ifbb,elsebb); | 202 gIR->scope() = IRScope(ifbb,elsebb); |
203 | 203 |
204 // do scoped statements | 204 // do scoped statements |
272 // replace current scope | 272 // replace current scope |
273 gIR->scope() = IRScope(whilebb,endbb); | 273 gIR->scope() = IRScope(whilebb,endbb); |
274 | 274 |
275 // create the condition | 275 // create the condition |
276 DValue* cond_e = condition->toElem(p); | 276 DValue* cond_e = condition->toElem(p); |
277 llvm::Value* cond_val = DtoBoolean(cond_e->getRVal()); | 277 LLValue* cond_val = DtoBoolean(cond_e->getRVal()); |
278 delete cond_e; | 278 delete cond_e; |
279 | 279 |
280 // conditional branch | 280 // conditional branch |
281 llvm::Value* ifbreak = llvm::BranchInst::Create(whilebodybb, endbb, cond_val, p->scopebb()); | 281 LLValue* ifbreak = llvm::BranchInst::Create(whilebodybb, endbb, cond_val, p->scopebb()); |
282 | 282 |
283 // rewrite scope | 283 // rewrite scope |
284 gIR->scope() = IRScope(whilebodybb,endbb); | 284 gIR->scope() = IRScope(whilebodybb,endbb); |
285 | 285 |
286 // while body code | 286 // while body code |
320 body->toIR(p); | 320 body->toIR(p); |
321 p->loopbbs.pop_back(); | 321 p->loopbbs.pop_back(); |
322 | 322 |
323 // create the condition | 323 // create the condition |
324 DValue* cond_e = condition->toElem(p); | 324 DValue* cond_e = condition->toElem(p); |
325 llvm::Value* cond_val = DtoBoolean(cond_e->getRVal()); | 325 LLValue* cond_val = DtoBoolean(cond_e->getRVal()); |
326 delete cond_e; | 326 delete cond_e; |
327 | 327 |
328 // conditional branch | 328 // conditional branch |
329 llvm::Value* ifbreak = llvm::BranchInst::Create(dowhilebb, endbb, cond_val, gIR->scopebb()); | 329 LLValue* ifbreak = llvm::BranchInst::Create(dowhilebb, endbb, cond_val, gIR->scopebb()); |
330 | 330 |
331 // rewrite the scope | 331 // rewrite the scope |
332 gIR->scope() = IRScope(endbb,oldend); | 332 gIR->scope() = IRScope(endbb,oldend); |
333 } | 333 } |
334 | 334 |
359 // replace current scope | 359 // replace current scope |
360 gIR->scope() = IRScope(forbb,forbodybb); | 360 gIR->scope() = IRScope(forbb,forbodybb); |
361 | 361 |
362 // create the condition | 362 // create the condition |
363 DValue* cond_e = condition->toElem(p); | 363 DValue* cond_e = condition->toElem(p); |
364 llvm::Value* cond_val = DtoBoolean(cond_e->getRVal()); | 364 LLValue* cond_val = DtoBoolean(cond_e->getRVal()); |
365 delete cond_e; | 365 delete cond_e; |
366 | 366 |
367 // conditional branch | 367 // conditional branch |
368 assert(!gIR->scopereturned()); | 368 assert(!gIR->scopereturned()); |
369 llvm::BranchInst::Create(forbodybb, endbb, cond_val, gIR->scopebb()); | 369 llvm::BranchInst::Create(forbodybb, endbb, cond_val, gIR->scopebb()); |
569 | 569 |
570 assert(exp); | 570 assert(exp); |
571 DValue* e = exp->toElem(p); | 571 DValue* e = exp->toElem(p); |
572 llvm::Function* fn = LLVM_D_GetRuntimeFunction(gIR->module, "_d_throw_exception"); | 572 llvm::Function* fn = LLVM_D_GetRuntimeFunction(gIR->module, "_d_throw_exception"); |
573 //Logger::cout() << "calling: " << *fn << '\n'; | 573 //Logger::cout() << "calling: " << *fn << '\n'; |
574 llvm::Value* arg = DtoBitCast(e->getRVal(), fn->getFunctionType()->getParamType(0)); | 574 LLValue* arg = DtoBitCast(e->getRVal(), fn->getFunctionType()->getParamType(0)); |
575 //Logger::cout() << "arg: " << *arg << '\n'; | 575 //Logger::cout() << "arg: " << *arg << '\n'; |
576 gIR->ir->CreateCall(fn, arg, ""); | 576 gIR->ir->CreateCall(fn, arg, ""); |
577 gIR->ir->CreateUnreachable(); | 577 gIR->ir->CreateUnreachable(); |
578 } | 578 } |
579 | 579 |
594 Case* c2 = (Case*)obj; | 594 Case* c2 = (Case*)obj; |
595 return str->compare(c2->str); | 595 return str->compare(c2->str); |
596 } | 596 } |
597 }; | 597 }; |
598 | 598 |
599 static llvm::Value* call_string_switch_runtime(llvm::GlobalVariable* table, Expression* e) | 599 static LLValue* call_string_switch_runtime(llvm::GlobalVariable* table, Expression* e) |
600 { | 600 { |
601 Type* dt = DtoDType(e->type); | 601 Type* dt = DtoDType(e->type); |
602 Type* dtnext = DtoDType(dt->next); | 602 Type* dtnext = DtoDType(dt->next); |
603 TY ty = dtnext->ty; | 603 TY ty = dtnext->ty; |
604 const char* fname; | 604 const char* fname; |
614 else { | 614 else { |
615 assert(0 && "not char/wchar/dchar"); | 615 assert(0 && "not char/wchar/dchar"); |
616 } | 616 } |
617 | 617 |
618 llvm::Function* fn = LLVM_D_GetRuntimeFunction(gIR->module, fname); | 618 llvm::Function* fn = LLVM_D_GetRuntimeFunction(gIR->module, fname); |
619 std::vector<llvm::Value*> args; | 619 std::vector<LLValue*> args; |
620 Logger::cout() << *table->getType() << '\n'; | 620 Logger::cout() << *table->getType() << '\n'; |
621 Logger::cout() << *fn->getFunctionType()->getParamType(0) << '\n'; | 621 Logger::cout() << *fn->getFunctionType()->getParamType(0) << '\n'; |
622 assert(table->getType() == fn->getFunctionType()->getParamType(0)); | 622 assert(table->getType() == fn->getFunctionType()->getParamType(0)); |
623 args.push_back(table); | 623 args.push_back(table); |
624 | 624 |
625 DValue* val = e->toElem(gIR); | 625 DValue* val = e->toElem(gIR); |
626 llvm::Value* llval; | 626 LLValue* llval; |
627 if (DSliceValue* sval = val->isSlice()) | 627 if (DSliceValue* sval = val->isSlice()) |
628 { | 628 { |
629 // give storage | 629 // give storage |
630 llval = new llvm::AllocaInst(DtoType(e->type), "tmp", gIR->topallocapoint()); | 630 llval = new llvm::AllocaInst(DtoType(e->type), "tmp", gIR->topallocapoint()); |
631 DVarValue* vv = new DVarValue(e->type, llval, true); | 631 DVarValue* vv = new DVarValue(e->type, llval, true); |
665 CaseStatement* last; | 665 CaseStatement* last; |
666 bool first = true; | 666 bool first = true; |
667 do { | 667 do { |
668 // integral case | 668 // integral case |
669 if (cs->exp->type->isintegral()) { | 669 if (cs->exp->type->isintegral()) { |
670 llvm::Constant* c = cs->exp->toConstElem(p); | 670 LLConstant* c = cs->exp->toConstElem(p); |
671 tmp.push_back(isaConstantInt(c)); | 671 tmp.push_back(isaConstantInt(c)); |
672 } | 672 } |
673 // string case | 673 // string case |
674 else { | 674 else { |
675 assert(cs->exp->op == TOKstring); | 675 assert(cs->exp->op == TOKstring); |
692 if (!condition->type->isintegral()) | 692 if (!condition->type->isintegral()) |
693 { | 693 { |
694 // first sort it | 694 // first sort it |
695 caseArray.sort(); | 695 caseArray.sort(); |
696 // iterate and add indices to cases | 696 // iterate and add indices to cases |
697 std::vector<llvm::Constant*> inits; | 697 std::vector<LLConstant*> inits; |
698 for (size_t i=0; i<caseArray.dim; ++i) | 698 for (size_t i=0; i<caseArray.dim; ++i) |
699 { | 699 { |
700 Case* c = (Case*)caseArray.data[i]; | 700 Case* c = (Case*)caseArray.data[i]; |
701 vcases[c->index].second.push_back(DtoConstUint(i)); | 701 vcases[c->index].second.push_back(DtoConstUint(i)); |
702 inits.push_back(c->str->toConstElem(p)); | 702 inits.push_back(c->str->toConstElem(p)); |
703 } | 703 } |
704 // build static array for ptr or final array | 704 // build static array for ptr or final array |
705 const llvm::Type* elemTy = DtoType(condition->type); | 705 const LLType* elemTy = DtoType(condition->type); |
706 const llvm::ArrayType* arrTy = llvm::ArrayType::get(elemTy, inits.size()); | 706 const llvm::ArrayType* arrTy = llvm::ArrayType::get(elemTy, inits.size()); |
707 llvm::Constant* arrInit = llvm::ConstantArray::get(arrTy, inits); | 707 LLConstant* arrInit = llvm::ConstantArray::get(arrTy, inits); |
708 llvm::GlobalVariable* arr = new llvm::GlobalVariable(arrTy, true, llvm::GlobalValue::InternalLinkage, arrInit, "string_switch_table_data", gIR->module); | 708 llvm::GlobalVariable* arr = new llvm::GlobalVariable(arrTy, true, llvm::GlobalValue::InternalLinkage, arrInit, "string_switch_table_data", gIR->module); |
709 | 709 |
710 const llvm::Type* elemPtrTy = getPtrToType(elemTy); | 710 const LLType* elemPtrTy = getPtrToType(elemTy); |
711 llvm::Constant* arrPtr = llvm::ConstantExpr::getBitCast(arr, elemPtrTy); | 711 LLConstant* arrPtr = llvm::ConstantExpr::getBitCast(arr, elemPtrTy); |
712 | 712 |
713 // build the static table | 713 // build the static table |
714 std::vector<const llvm::Type*> types; | 714 std::vector<const LLType*> types; |
715 types.push_back(DtoSize_t()); | 715 types.push_back(DtoSize_t()); |
716 types.push_back(elemPtrTy); | 716 types.push_back(elemPtrTy); |
717 const llvm::StructType* sTy = llvm::StructType::get(types); | 717 const llvm::StructType* sTy = llvm::StructType::get(types); |
718 std::vector<llvm::Constant*> sinits; | 718 std::vector<LLConstant*> sinits; |
719 sinits.push_back(DtoConstSize_t(inits.size())); | 719 sinits.push_back(DtoConstSize_t(inits.size())); |
720 sinits.push_back(arrPtr); | 720 sinits.push_back(arrPtr); |
721 llvm::Constant* sInit = llvm::ConstantStruct::get(sTy, sinits); | 721 LLConstant* sInit = llvm::ConstantStruct::get(sTy, sinits); |
722 | 722 |
723 switchTable = new llvm::GlobalVariable(sTy, true, llvm::GlobalValue::InternalLinkage, sInit, "string_switch_table", gIR->module); | 723 switchTable = new llvm::GlobalVariable(sTy, true, llvm::GlobalValue::InternalLinkage, sInit, "string_switch_table", gIR->module); |
724 } | 724 } |
725 | 725 |
726 // default | 726 // default |
732 | 732 |
733 // end (break point) | 733 // end (break point) |
734 llvm::BasicBlock* endbb = llvm::BasicBlock::Create("switchend", p->topfunc(), oldend); | 734 llvm::BasicBlock* endbb = llvm::BasicBlock::Create("switchend", p->topfunc(), oldend); |
735 | 735 |
736 // condition var | 736 // condition var |
737 llvm::Value* condVal; | 737 LLValue* condVal; |
738 // integral switch | 738 // integral switch |
739 if (condition->type->isintegral()) { | 739 if (condition->type->isintegral()) { |
740 DValue* cond = condition->toElem(p); | 740 DValue* cond = condition->toElem(p); |
741 condVal = cond->getRVal(); | 741 condVal = cond->getRVal(); |
742 } | 742 } |
843 //Logger::println("Argument is %s", arg->toChars()); | 843 //Logger::println("Argument is %s", arg->toChars()); |
844 | 844 |
845 Logger::println("aggr = %s", aggr->toChars()); | 845 Logger::println("aggr = %s", aggr->toChars()); |
846 | 846 |
847 // key | 847 // key |
848 const llvm::Type* keytype = key ? DtoType(key->type) : DtoSize_t(); | 848 const LLType* keytype = key ? DtoType(key->type) : DtoSize_t(); |
849 llvm::Value* keyvar = new llvm::AllocaInst(keytype, "foreachkey", p->topallocapoint()); | 849 LLValue* keyvar = new llvm::AllocaInst(keytype, "foreachkey", p->topallocapoint()); |
850 if (key) | 850 if (key) |
851 { | 851 { |
852 //key->llvmValue = keyvar; | 852 //key->llvmValue = keyvar; |
853 assert(!key->ir.irLocal); | 853 assert(!key->ir.irLocal); |
854 key->ir.irLocal = new IrLocal(key); | 854 key->ir.irLocal = new IrLocal(key); |
855 key->ir.irLocal->value = keyvar; | 855 key->ir.irLocal->value = keyvar; |
856 } | 856 } |
857 llvm::Value* zerokey = llvm::ConstantInt::get(keytype,0,false); | 857 LLValue* zerokey = llvm::ConstantInt::get(keytype,0,false); |
858 | 858 |
859 // value | 859 // value |
860 Logger::println("value = %s", value->toPrettyChars()); | 860 Logger::println("value = %s", value->toPrettyChars()); |
861 const llvm::Type* valtype = DtoType(value->type); | 861 const LLType* valtype = DtoType(value->type); |
862 llvm::Value* valvar = NULL; | 862 LLValue* valvar = NULL; |
863 if (!value->isRef() && !value->isOut()) | 863 if (!value->isRef() && !value->isOut()) |
864 valvar = new llvm::AllocaInst(valtype, "foreachval", p->topallocapoint()); | 864 valvar = new llvm::AllocaInst(valtype, "foreachval", p->topallocapoint()); |
865 if (!value->ir.irLocal) | 865 if (!value->ir.irLocal) |
866 value->ir.irLocal = new IrLocal(value); | 866 value->ir.irLocal = new IrLocal(value); |
867 | 867 |
868 // what to iterate | 868 // what to iterate |
869 DValue* aggrval = aggr->toElem(p); | 869 DValue* aggrval = aggr->toElem(p); |
870 Type* aggrtype = DtoDType(aggr->type); | 870 Type* aggrtype = DtoDType(aggr->type); |
871 | 871 |
872 // get length and pointer | 872 // get length and pointer |
873 llvm::Value* val = 0; | 873 LLValue* val = 0; |
874 llvm::Value* niters = 0; | 874 LLValue* niters = 0; |
875 | 875 |
876 // static array | 876 // static array |
877 if (aggrtype->ty == Tsarray) | 877 if (aggrtype->ty == Tsarray) |
878 { | 878 { |
879 Logger::println("foreach over static array"); | 879 Logger::println("foreach over static array"); |
920 niters = gIR->ir->CreateTrunc(niters, keytype, "foreachtrunckey"); | 920 niters = gIR->ir->CreateTrunc(niters, keytype, "foreachtrunckey"); |
921 else | 921 else |
922 niters = gIR->ir->CreateBitCast(niters, keytype, "foreachtrunckey"); | 922 niters = gIR->ir->CreateBitCast(niters, keytype, "foreachtrunckey"); |
923 } | 923 } |
924 | 924 |
925 llvm::Constant* delta = 0; | 925 LLConstant* delta = 0; |
926 if (op == TOKforeach) { | 926 if (op == TOKforeach) { |
927 new llvm::StoreInst(zerokey, keyvar, p->scopebb()); | 927 new llvm::StoreInst(zerokey, keyvar, p->scopebb()); |
928 } | 928 } |
929 else { | 929 else { |
930 new llvm::StoreInst(niters, keyvar, p->scopebb()); | 930 new llvm::StoreInst(niters, keyvar, p->scopebb()); |
939 llvm::BranchInst::Create(condbb, p->scopebb()); | 939 llvm::BranchInst::Create(condbb, p->scopebb()); |
940 | 940 |
941 // condition | 941 // condition |
942 p->scope() = IRScope(condbb,bodybb); | 942 p->scope() = IRScope(condbb,bodybb); |
943 | 943 |
944 llvm::Value* done = 0; | 944 LLValue* done = 0; |
945 llvm::Value* load = new llvm::LoadInst(keyvar, "tmp", p->scopebb()); | 945 LLValue* load = new llvm::LoadInst(keyvar, "tmp", p->scopebb()); |
946 if (op == TOKforeach) { | 946 if (op == TOKforeach) { |
947 done = new llvm::ICmpInst(llvm::ICmpInst::ICMP_ULT, load, niters, "tmp", p->scopebb()); | 947 done = new llvm::ICmpInst(llvm::ICmpInst::ICMP_ULT, load, niters, "tmp", p->scopebb()); |
948 } | 948 } |
949 else if (op == TOKforeach_reverse) { | 949 else if (op == TOKforeach_reverse) { |
950 done = new llvm::ICmpInst(llvm::ICmpInst::ICMP_UGT, load, zerokey, "tmp", p->scopebb()); | 950 done = new llvm::ICmpInst(llvm::ICmpInst::ICMP_UGT, load, zerokey, "tmp", p->scopebb()); |
955 | 955 |
956 // init body | 956 // init body |
957 p->scope() = IRScope(bodybb,nextbb); | 957 p->scope() = IRScope(bodybb,nextbb); |
958 | 958 |
959 // get value for this iteration | 959 // get value for this iteration |
960 llvm::Constant* zero = llvm::ConstantInt::get(keytype,0,false); | 960 LLConstant* zero = llvm::ConstantInt::get(keytype,0,false); |
961 llvm::Value* loadedKey = p->ir->CreateLoad(keyvar,"tmp"); | 961 LLValue* loadedKey = p->ir->CreateLoad(keyvar,"tmp"); |
962 if (aggrtype->ty == Tsarray) | 962 if (aggrtype->ty == Tsarray) |
963 value->ir.irLocal->value = DtoGEP(val,zero,loadedKey,"tmp"); | 963 value->ir.irLocal->value = DtoGEP(val,zero,loadedKey,"tmp"); |
964 else if (aggrtype->ty == Tarray) | 964 else if (aggrtype->ty == Tarray) |
965 value->ir.irLocal->value = llvm::GetElementPtrInst::Create(val,loadedKey,"tmp",p->scopebb()); | 965 value->ir.irLocal->value = llvm::GetElementPtrInst::Create(val,loadedKey,"tmp",p->scopebb()); |
966 | 966 |
980 llvm::BranchInst::Create(nextbb, p->scopebb()); | 980 llvm::BranchInst::Create(nextbb, p->scopebb()); |
981 | 981 |
982 // next | 982 // next |
983 p->scope() = IRScope(nextbb,endbb); | 983 p->scope() = IRScope(nextbb,endbb); |
984 if (op == TOKforeach) { | 984 if (op == TOKforeach) { |
985 llvm::Value* load = DtoLoad(keyvar); | 985 LLValue* load = DtoLoad(keyvar); |
986 load = p->ir->CreateAdd(load, llvm::ConstantInt::get(keytype, 1, false), "tmp"); | 986 load = p->ir->CreateAdd(load, llvm::ConstantInt::get(keytype, 1, false), "tmp"); |
987 DtoStore(load, keyvar); | 987 DtoStore(load, keyvar); |
988 } | 988 } |
989 llvm::BranchInst::Create(condbb, p->scopebb()); | 989 llvm::BranchInst::Create(condbb, p->scopebb()); |
990 | 990 |
1139 } while (t = t->next); | 1139 } while (t = t->next); |
1140 | 1140 |
1141 Logger::println("asm expr = '%s'", asmstr.c_str()); | 1141 Logger::println("asm expr = '%s'", asmstr.c_str()); |
1142 | 1142 |
1143 // create function type | 1143 // create function type |
1144 std::vector<const llvm::Type*> args; | 1144 std::vector<const LLType*> args; |
1145 const llvm::FunctionType* fty = llvm::FunctionType::get(DtoSize_t(), args, false); | 1145 const llvm::FunctionType* fty = llvm::FunctionType::get(DtoSize_t(), args, false); |
1146 | 1146 |
1147 // create inline asm callee | 1147 // create inline asm callee |
1148 llvm::InlineAsm* inasm = llvm::InlineAsm::get(fty, asmstr, "r,r", false); | 1148 llvm::InlineAsm* inasm = llvm::InlineAsm::get(fty, asmstr, "r,r", false); |
1149 | 1149 |