rendered paste bodyIndex: pcsx2/NakedAsm.h===================================================================--- pcsx2/NakedAsm.h (revision 781)+++ pcsx2/NakedAsm.h (working copy)@@ -54,15 +54,11 @@ // aR3000A.S void iopJITCompile(); void iopJITCompileInBlock();-void iopDispatcher();-void iopDispatcherClear(); void iopDispatcherReg(); // aR5900-32.S void JITCompile(); void JITCompileInBlock();-void Dispatcher();-void DispatcherClear(); void DispatcherReg(); }Index: pcsx2/x86/BaseblockEx.cpp===================================================================--- pcsx2/x86/BaseblockEx.cpp (revision 781)+++ pcsx2/x86/BaseblockEx.cpp (working copy)@@ -19,7 +19,7 @@ #include "PrecompiledHeader.h" #include "BaseblockEx.h" -BASEBLOCKEX* BaseBlocks::New(u32 startpc)+BASEBLOCKEX* BaseBlocks::New(u32 startpc, uptr fnptr) { if (blocks.size() == size) return 0;@@ -28,6 +28,7 @@ std::vector<BASEBLOCKEX>::iterator iter; memset(&newblock, 0, sizeof newblock); newblock.startpc = startpc;+ newblock.fnptr = fnptr; int imin = 0, imax = blocks.size(), imid; @@ -42,6 +43,11 @@ assert(imin == blocks.size() || blocks[imin].startpc > startpc); iter = blocks.insert(blocks.begin() + imin, newblock);++ std::pair<linkiter_t, linkiter_t> range = links.equal_range(startpc);+ for (linkiter_t i = range.first; i != range.second; ++i)+ *(u32*)i->second = fnptr - (i->second + 4);+ return &*iter; } @@ -63,3 +69,19 @@ return imin; }++BASEBLOCKEX* BaseBlocks::GetByX86(uptr ip) const+{+ // TODO+ return 0;+}++void BaseBlocks::Link(u32 pc, uptr jumpptr)+{+ BASEBLOCKEX *targetblock = Get(pc);+ if (targetblock && targetblock->startpc == pc)+ *(u32*)jumpptr = targetblock->fnptr - (jumpptr + 4);+ else+ *(u32*)jumpptr = recompiler - (jumpptr + 4);+ links.insert(std::pair<u32, uptr>(pc, jumpptr));+}\ No newline at end of fileIndex: pcsx2/x86/BaseblockEx.h===================================================================--- pcsx2/x86/BaseblockEx.h (revision 781)+++ pcsx2/x86/BaseblockEx.h (working copy)@@ -20,6 +20,8 @@ #include "PrecompiledHeader.h" #include <vector>+#include <map>+#include <utility> // used to keep block information #define BLOCKTYPE_DELAYSLOT 1 // if bit set, delay slot@@ -38,9 +40,10 @@ // extra block info (only valid for start of fn) struct BASEBLOCKEX {- u16 size; // size in dwords - u16 dummy; u32 startpc;+ uptr fnptr;+ u16 size; // size in dwords+ u16 x86size; #ifdef PCSX2_DEVBUILD u32 visited; // number of times called@@ -53,24 +56,30 @@ { private: std::vector<BASEBLOCKEX> blocks;+ // switch to a hash map later?+ std::multimap<u32, uptr> links;+ typedef std::multimap<u32, uptr>::iterator linkiter_t; unsigned long size;+ uptr recompiler; public:- BaseBlocks(unsigned long max) :- size(max),+ BaseBlocks(unsigned long size_, uptr recompiler_) :+ size(size_),+ recompiler(recompiler_), blocks(0) { blocks.reserve(size); } - BASEBLOCKEX* New(u32 startpc);+ BASEBLOCKEX* New(u32 startpc, uptr fnptr); int LastIndex (u32 startpc) const;+ BASEBLOCKEX* GetByX86(uptr ip) const; inline int Index (u32 startpc) const { int idx = LastIndex(startpc); if (idx == -1 || startpc < blocks[idx].startpc ||- startpc >= blocks[idx].startpc + blocks[idx].size * 4)+ blocks[idx].size && (startpc >= blocks[idx].startpc + blocks[idx].size * 4)) return -1; else return idx;@@ -90,12 +99,20 @@ inline void Remove(int idx) {+ u32 startpc = blocks[idx].startpc;+ std::pair<linkiter_t, linkiter_t> range = links.equal_range(blocks[idx].startpc);+ for (linkiter_t i = range.first; i != range.second; ++i)+ *(u32*)i->second = recompiler - (i->second + 4);+ // TODO: remove links from this block? blocks.erase(blocks.begin() + idx); } + void Link(u32 pc, uptr jumpptr);+ inline void Reset() { blocks.clear();+ links.clear(); } }; Index: pcsx2/x86/iR3000A.cpp===================================================================--- pcsx2/x86/iR3000A.cpp (revision 781)+++ pcsx2/x86/iR3000A.cpp (working copy)@@ -60,7 +60,8 @@ static BASEBLOCK *recRAM = NULL; // and the ptr to the blocks here static BASEBLOCK *recROM = NULL; // and here static BASEBLOCK *recROM1 = NULL; // also here-static BaseBlocks recBlocks(PSX_NUMBLOCKS);+void iopJITCompile();+static BaseBlocks recBlocks(PSX_NUMBLOCKS, (uptr)iopJITCompile); static u8 *recPtr = NULL; u32 psxpc; // recompiler psxpc int psxbranch; // set for branch@@ -621,77 +622,6 @@ } } -// jumped to when an immediate branch (EE side) hasn't been statically linked yet.-// Block is compiled if needed, and the link is made.-// EDX contains the jump addr to modify-static __declspec(naked) void iopDispatcher()-{- __asm {- mov eax, dword ptr [psxRegs.pc]- mov ebx, eax- shr eax, 16- mov ecx, dword ptr [psxRecLUT+eax*4]- mov eax, dword ptr [ecx+ebx]- cmp eax, offset iopJITCompile- je notcompiled- cmp eax, offset iopJITCompileInBlock- je notcompiled- lea ebx, [eax-4]- sub ebx, edx- mov dword ptr [edx], ebx- jmp eax-- align 16-notcompiled:- mov esi, edx- lea edi, [ecx+ebx]- push ebx- call iopRecRecompile- add esp, 4-- mov eax, dword ptr [edi]- lea ebx, [eax-4]- sub ebx, esi- mov dword ptr [esi], ebx- jmp eax- }-}--// edx - baseblock start pc-// stack - x86Ptr[0]-static __declspec(naked) void iopDispatcherClear()-{- __asm {- mov [psxRegs.pc], edx- mov ebx, edx- shr edx, 16- mov ecx, dword ptr [psxRecLUT+edx*4]- mov eax, dword ptr [ecx+ebx]- cmp eax, offset iopJITCompile- je notcompiled- cmp eax, offset iopJITCompileInBlock- je notcompiled- add esp, 4- jmp eax-- align 16-notcompiled:- lea edi, [ecx+ebx]- push ebx- call iopRecRecompile- add esp, 4- mov eax, dword ptr [edi]-- pop ecx- mov byte ptr [ecx], 0xe9 // jmp32- lea ebx, [eax-5]- sub ebx, ecx- mov dword ptr [ecx+1], ebx-- jmp eax- }-}- // called when jumping to variable psxpc address static __declspec(naked) void iopDispatcherReg() {@@ -793,28 +723,6 @@ if (pexblock->startpc >= upperextent) break; - pblock = PSX_GETBLOCK(pexblock->startpc);- x86Ptr[_EmitterId_] = (u8*)pblock->GetFnptr();-- jASSUME((u8*)iopJITCompile != x86Ptr[_EmitterId_]);- // jASSUME((u8*)iopJITCompileInside != x86Ptr[_EmitterId_]);-- // This is breaking things currently, rather than figure it out- // I'm just using DispatcherReg, it's fast enough now.- // Actually, if we want to do this at all maybe keeping a hash- // table of const jumps and modifying the jumps straight from- // here is the way to go.-#if 1- // there is a small problem: mem can be ored with 0xa<<28 or 0x8<<28, and don't know which- MOV32ItoR(EDX, pexblock->startpc);- assert((uptr)x86Ptr[_EmitterId_] <= 0xffffffff);- PUSH32I((uptr)x86Ptr[_EmitterId_]); // will be replaced by JMP32- JMP32((uptr)iopDispatcherClear - ((uptr)x86Ptr[_EmitterId_] + 5));-#else- MOV32ItoM((uptr)&psxRegs.pc, pexblock->startpc);- JMP32((uptr)iopDispatcherReg - ((uptr)x86Ptr[_EmitterId_] + 5));-#endif- lowerextent = min(lowerextent, pexblock->startpc); upperextent = max(upperextent, pexblock->startpc + pexblock->size * 4); recBlocks.Remove(blockidx);@@ -878,9 +786,8 @@ _psxFlushCall(FLUSH_EVERYTHING); iPsxBranchTest(imm, imm <= psxpc); - MOV32ItoR(EDX, 0);- ptr = (u32*)(x86Ptr[0]-4);- *ptr = (uptr)JMP32((uptr)iopDispatcher - ( (uptr)x86Ptr[0] + 5 ));+ ptr = JMP32(0);+ recBlocks.Link(imm, (uptr)ptr); } //fixme : this is all a huge hack, we base the counter advancements on the average an opcode should take (wtf?)@@ -989,24 +896,6 @@ BASEBLOCK* pblock = PSX_GETBLOCK(psxpc); - // need *ppblock != s_pCurBlock because of branches- if (HWADDR(psxpc) != s_pCurBlockEx->startpc- && pblock->GetFnptr() != (uptr)iopJITCompile- && pblock->GetFnptr() != (uptr)iopJITCompileInBlock )- {- if(!delayslot)- {- // code already in place, so jump to it and exit recomp- assert( recBlocks.Get(HWADDR(psxpc))->startpc == HWADDR(psxpc) );- - _psxFlushCall(FLUSH_EVERYTHING);- MOV32ItoM((uptr)&psxRegs.pc, psxpc);- JMP32((uptr)pblock->GetFnptr() - ((uptr)x86Ptr[0] + 5));- psxbranch = 3;- return;- }- }- #ifdef _DEBUG MOV32ItoR(EAX, psxpc); #endif@@ -1101,7 +990,6 @@ u32 i; u32 branchTo; u32 willbranch3 = 0;- u32* ptr; #ifdef _DEBUG if( psxdump & 4 )@@ -1114,6 +1002,10 @@ if (((uptr)recPtr - (uptr)recMem) >= (RECMEM_SIZE - 0x10000)) recResetIOP(); + x86SetPtr( recPtr );+ x86Align(16);+ recPtr = x86Ptr[_EmitterId_];+ s_pCurBlock = PSX_GETBLOCK(startpc); assert(s_pCurBlock->GetFnptr() == (uptr)iopJITCompile@@ -1121,19 +1013,16 @@ s_pCurBlockEx = recBlocks.Get(HWADDR(startpc)); if(!s_pCurBlockEx || s_pCurBlockEx->startpc != HWADDR(startpc)) {- s_pCurBlockEx = recBlocks.New(HWADDR(startpc));+ s_pCurBlockEx = recBlocks.New(HWADDR(startpc), (uptr)recPtr); if( s_pCurBlockEx == NULL ) { DevCon::WriteLn("IOP Recompiler data reset"); recResetIOP();- s_pCurBlockEx = recBlocks.New(HWADDR(startpc));+ x86SetPtr( recPtr );+ s_pCurBlockEx = recBlocks.New(HWADDR(startpc), (uptr)recPtr); } } - x86SetPtr( recPtr );- x86Align(16);- recPtr = x86Ptr[0];- psxbranch = 0; s_pCurBlock->SetFnptr( (uptr)x86Ptr[0] );@@ -1274,7 +1163,6 @@ JMP32((uptr)iopDispatcherReg - ( (uptr)x86Ptr[0] + 5 )); } else {- assert( psxbranch != 3 ); if( psxbranch ) assert( !willbranch3 ); else {@@ -1282,42 +1170,27 @@ SUB32ItoM((uptr)&psxCycleEE, psxScaleBlockCycles()*8 ); } - if( willbranch3 ) {- BASEBLOCK* pblock = PSX_GETBLOCK(s_nEndBlock);+ if (willbranch3 || !psxbranch) { assert( psxpc == s_nEndBlock ); _psxFlushCall(FLUSH_EVERYTHING);- MOV32ItoM((uptr)&psxRegs.pc, psxpc); - JMP32((uptr)pblock->GetFnptr() - ((uptr)x86Ptr[0] + 5));+ MOV32ItoM((uptr)&psxRegs.pc, psxpc);+ u32 *ptr = JMP32(0);+ recBlocks.Link(s_nEndBlock, (uptr)ptr); psxbranch = 3; }- else if( !psxbranch ) {- // didn't branch, but had to stop- MOV32ItoM( (uptr)&psxRegs.pc, psxpc );-- _psxFlushCall(FLUSH_EVERYTHING);-- ptr = JMP32(0);- //JMP32((uptr)psxDispatcherReg - ( (uptr)x86Ptr[0] + 5 ));- } } assert( x86Ptr[0] < recMem+RECMEM_SIZE ); + assert(x86Ptr[_EmitterId_] - recPtr < 0x10000);+ s_pCurBlockEx->x86size = x86Ptr[_EmitterId_] - recPtr;+ recPtr = x86Ptr[0]; assert( (g_psxHasConstReg&g_psxFlushedConstReg) == g_psxHasConstReg ); - if( !psxbranch ) {- assert( ptr != NULL );- s_pCurBlock = PSX_GETBLOCK(psxpc);-- if (s_pCurBlock->GetFnptr() == (uptr)iopJITCompile- || s_pCurBlock->GetFnptr() == (uptr)iopJITCompileInBlock){- iopRecRecompile(psxpc);- }-- *ptr = s_pCurBlock->GetFnptr() - ((u32)ptr + 4);- }+ s_pCurBlock = NULL;+ s_pCurBlockEx = NULL; } R3000Acpu psxRec = {Index: pcsx2/x86/ix86-32/iR5900-32.cpp===================================================================--- pcsx2/x86/ix86-32/iR5900-32.cpp (revision 781)+++ pcsx2/x86/ix86-32/iR5900-32.cpp (working copy)@@ -79,7 +79,8 @@ static BASEBLOCK *recROM = NULL; // and here static BASEBLOCK *recROM1 = NULL; // also here static u32 *recRAMCopy = NULL;-static BaseBlocks recBlocks(EE_NUMBLOCKS);+void JITCompile();+static BaseBlocks recBlocks(EE_NUMBLOCKS, (uptr)JITCompile); static u8* recPtr = NULL, *recStackPtr = NULL; static EEINST* s_pInstCache = NULL; static u32 s_nInstCacheSize = 0;@@ -111,7 +112,7 @@ //static const char *txt2 = "M32 = %x\n"; #endif -static void iBranchTest(u32 newpc, bool noDispatch=false);+static void iBranchTest(u32 newpc = 0xffffffff, bool noDispatch=false); static void ClearRecLUT(BASEBLOCK* base, int count); ////////////////////////////////////////////////////@@ -446,7 +447,6 @@ return (u32*)(recStackPtr-size); } - static const int REC_CACHEMEM = 0x01000000; static void __fastcall dyna_block_discard(u32 start,u32 sz); @@ -600,6 +600,7 @@ safe_aligned_free( m_recBlockAlloc ); recRAM = recROM = recROM1 = NULL; recStack = NULL;+ recRAMCopy = NULL; safe_free( s_pInstCache ); s_nInstCacheSize = 0;@@ -662,79 +663,6 @@ } } -// jumped to when an immediate branch (EE side) hasn't been statically linked yet.-// Block is compiled if needed, and the link is made.-// EDX contains the jump addr to modify-static __naked void Dispatcher()-{- __asm {- mov eax, dword ptr [cpuRegs.pc]- mov ebx, eax- shr eax, 16- mov ecx, dword ptr [recLUT+eax*4]- mov eax, dword ptr [ecx+ebx]-- cmp eax, offset JITCompile- je notcompiled- cmp eax, offset JITCompileInBlock- je notcompiled- lea ebx, [eax-4]- sub ebx, edx- mov dword ptr [edx], ebx- jmp eax-- align 16-notcompiled:- mov esi, edx- lea edi, [ecx+ebx]- push ebx- call recRecompile- add esp, 4-- mov eax, dword ptr [edi]- lea ebx, [eax-4]- sub ebx, esi- mov dword ptr [esi], ebx- jmp eax- }-}--// edx - block start pc-// stack - x86Ptr[0]-static __naked void DispatcherClear()-{- __asm {- mov [cpuRegs.pc], edx- mov ebx, edx- shr edx, 16- mov ecx, dword ptr [recLUT+edx*4]- mov eax, dword ptr [ecx+ebx]-- cmp eax, offset JITCompile- je notcompiled- cmp eax, offset JITCompileInBlock- je notcompiled- add esp, 4- jmp eax-- align 16-notcompiled:- lea edi, [ecx+ebx]- push ebx- call recRecompile- add esp, 4- mov eax, dword ptr [edi]-- pop ecx- mov byte ptr [ecx], 0xe9 // jmp32- lea ebx, [eax-5]- sub ebx, ecx- mov dword ptr [ecx+1], ebx-- jmp eax- }-}- // called when jumping to variable pc address static void __naked DispatcherReg() {@@ -943,21 +871,6 @@ break; } - x86Ptr[_EmitterId_] = (u8*)pblock->GetFnptr();-- jASSUME((u8*)JITCompile != x86Ptr[_EmitterId_]);- jASSUME((u8*)JITCompileInBlock != x86Ptr[_EmitterId_]);-- // Actually, if we want to do this at all maybe keeping a hash- // table of const jumps and modifying the jumps straight from- // here is the way to go.-- // there is a small problem: mem can be ored with 0xa<<28 or 0x8<<28, and don't know which- MOV32ItoR(EDX, blockstart);- assert((uptr)x86Ptr[_EmitterId_] <= 0xffffffff);- PUSH32I((uptr)x86Ptr[_EmitterId_]); // will be replaced by JMP32- JMP32((uptr)DispatcherClear - ((uptr)x86Ptr[_EmitterId_] + 5));- lowerextent = min(lowerextent, blockstart); upperextent = max(upperextent, blockend); // This might end up inside a block that doesn't contain the clearing range,@@ -1055,7 +968,7 @@ iFlushCall(FLUSH_EVERYTHING); - iBranchTest(0xffffffff);+ iBranchTest(); } void SetBranchImm( u32 imm )@@ -1065,9 +978,7 @@ assert( imm ); // end the current block- MOV32ItoM( (uptr)&cpuRegs.pc, imm ); iFlushCall(FLUSH_EVERYTHING);- iBranchTest(imm); } @@ -1197,6 +1108,19 @@ return temp >> (3+2); } +static void iBranch(u32 newpc, int type)+{+ u32* ptr;++ MOV32ItoM((uptr)&cpuRegs.pc, newpc);+ if (type == 0)+ ptr = JMP32(0);+ else if (type == 1)+ ptr = JS32(0);++ recBlocks.Link(HWADDR(newpc), (uptr)ptr);+}+ // Generates dynarec code for Event tests followed by a block dispatch (branch). // Parameters: // newpc - address to jump to at the end of the block. If newpc == 0xffffffff then@@ -1212,42 +1136,24 @@ #ifdef _DEBUG //CALLFunc((uptr)testfpu); #endif- u32* ptr; if( bExecBIOS ) CheckForBIOSEnd(); - MOV32MtoR(EAX, (uptr)&cpuRegs.cycle);- if( !noDispatch && newpc != 0xffffffff )- {- // Optimization note: Instructions order to pair EDX with EAX's load above.-- // Load EDX with the address of the JS32 jump below.- // We do this because the the Dispatcher will use this info to modify- // the JS instruction later on with the address of the block it's jumping- // to; creating a static link of blocks that doesn't require the overhead- // of a dispatcher.- MOV32ItoR(EDX, 0);- ptr = (u32*)(x86Ptr[0]-4);- }- // Check the Event scheduler if our "cycle target" has been reached. // Equiv code to: // cpuRegs.cycle += blockcycles; // if( cpuRegs.cycle > g_nextBranchCycle ) { DoEvents(); }+ MOV32MtoR(EAX, (uptr)&cpuRegs.cycle); ADD32ItoR(EAX, eeScaleBlockCycles()); MOV32RtoM((uptr)&cpuRegs.cycle, EAX); // update cycles SUB32MtoR(EAX, (uptr)&g_nextBranchCycle); - if( newpc != 0xffffffff )- {- // This is the jump instruction which gets modified by Dispatcher.- *ptr = (u32)JS32((u32)Dispatcher - ( (u32)x86Ptr[0] + 6 ));+ if (!noDispatch) {+ if (newpc == 0xffffffff)+ JS32((uptr)DispatcherReg - ( (uptr)x86Ptr[0] + 6 ));+ else+ iBranch(newpc, 1); }- else if( !noDispatch )- {- // This instruction is a dynamic link, so it's never modified.- JS32((uptr)DispatcherReg - ( (uptr)x86Ptr[0] + 6 ));- } RET(); }@@ -1449,7 +1355,6 @@ u32 i = 0; u32 branchTo; u32 willbranch3 = 0;- u32* ptr; u32 usecop2; #ifdef _DEBUG@@ -1470,28 +1375,29 @@ recResetEE(); } + x86SetPtr( recPtr );+ x86Align(16);+ recPtr = x86Ptr[_EmitterId_];+ s_pCurBlock = PC_GETBLOCK(startpc); - assert(s_pCurBlock->GetFnptr() == (uptr)JITCompile+ jASSUME(s_pCurBlock->GetFnptr() == (uptr)JITCompile || s_pCurBlock->GetFnptr() == (uptr)JITCompileInBlock); s_pCurBlockEx = recBlocks.Get(HWADDR(startpc)); assert(!s_pCurBlockEx || s_pCurBlockEx->startpc != HWADDR(startpc)); - s_pCurBlockEx = recBlocks.New(HWADDR(startpc));+ s_pCurBlockEx = recBlocks.New(HWADDR(startpc), (uptr)recPtr); if( s_pCurBlockEx == NULL ) { //SysPrintf("ee reset (blocks)\n"); recResetEE();- s_pCurBlockEx = recBlocks.New(HWADDR(startpc));+ x86SetPtr( recPtr );+ s_pCurBlockEx = recBlocks.New(HWADDR(startpc), (uptr)recPtr); } assert(s_pCurBlockEx); - x86SetPtr( recPtr );- x86Align(16);- recPtr = x86Ptr[0];- branch = 0; // reset recomp state variables@@ -1852,9 +1758,7 @@ break; if (memcmp(&recRAMCopy[oldBlock->startpc / 4], PSM(oldBlock->startpc), oldBlock->size * 4)) {- u8* oldX86 = x86Ptr[_EmitterId_]; recClear(startpc, (pc - startpc) / 4);- x86Ptr[_EmitterId_] = oldX86; s_pCurBlockEx = recBlocks.Get(HWADDR(startpc)); assert(s_pCurBlockEx->startpc == HWADDR(startpc)); break;@@ -1893,42 +1797,23 @@ else ADD32ItoM((int)&cpuRegs.cycle, eeScaleBlockCycles() ); - if( willbranch3 ) {- BASEBLOCK* pblock = PC_GETBLOCK(s_nEndBlock);- assert( pc == s_nEndBlock );+ if( willbranch3 || !branch) { iFlushCall(FLUSH_EVERYTHING);- MOV32ItoM((uptr)&cpuRegs.pc, pc);- JMP32((uptr)pblock->GetFnptr() - ((uptr)x86Ptr[0] + 5));- branch = 3;+ iBranch(pc, 0); }- else if( !branch ) {- // didn't branch, but had to stop- MOV32ItoM( (uptr)&cpuRegs.pc, pc );-- iFlushCall(FLUSH_EVERYTHING);-- ptr = JMP32(0);- } } assert( x86Ptr[0] < recMem+REC_CACHEMEM ); assert( recStackPtr < recStack+RECSTACK_SIZE ); assert( x86FpuState == 0 ); + assert(x86Ptr[_EmitterId_] - recPtr < 0x10000);+ s_pCurBlockEx->x86size = x86Ptr[_EmitterId_] - recPtr;+ recPtr = x86Ptr[0]; assert( (g_cpuHasConstReg&g_cpuFlushedConstReg) == g_cpuHasConstReg ); - if( !branch ) {- assert( ptr != NULL );- BASEBLOCK *pblock = PC_GETBLOCK(pc);-- if (pblock->GetFnptr() == (uptr)JITCompile- || pblock->GetFnptr() == (uptr)JITCompileInBlock)- recRecompile(pc);- *ptr = pblock->GetFnptr() - ((u32)ptr + 4);- }- s_pCurBlock = NULL; s_pCurBlockEx = NULL; }@@ -1941,4 +1826,4 @@ recExecuteBlock, recClear, recShutdown-};+};\ No newline at end of file