1"use strict";(self.webpackChunkpcsx_2_net=self.webpackChunkpcsx_2_net||[]).push([["44474"],{18194:function(e,t,n){n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>a,default:()=>d,frontMatter:()=>i,metadata:()=>o,toc:()=>l});var o=n(17791),s=n(85893),r=n(50065);let i={authors:["cottonvibes"],date:new Date("2010-03-13T00:00:00.000Z"),description:"his blog post is an introduction to dynamic recompilers (dynarecs), and hopes to provide some insight on how they work and why pcsx2 uses them to speed up emulation",draft:!1,tags:["devblog"],title:"Introduction To Dynamic Recompilation"},a=void 0,c={authorsImageUrls:[void 0]},l=[];function h(e){let t={a:"a",code:"code",em:"em",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,r.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(t.p,{children:["This blog post is an introduction to dynamic recompilers (dynarecs), and\nhopes to provide some insight on how they work and why pcsx2 uses them\nto speed up emulation.\nIt is probably easier to read on our forums, because some of the code\ndidn't wrap nicely on our main blog page....\n( ",(0,s.jsx)(t.a,{href:"http://forums.pcsx2.net/thread-13453-post-101560.html",children:"Click here to view blog post in\nforum"})," )"]}),"\n",(0,s.jsx)(t.p,{children:"To first understand why dynarecs are useful, you must first be familiar\nwith a basic interpreter emulator."}),"\n",(0,s.jsx)(t.p,{children:"Assume we are emulating a very simple processor. Processors have\ninstruction sets which are a set of different instructions they can\ncompute.\nLets assume the processor we are emulating is a made-up chip I'll call\nSL3 (super lame 3), and has only these 3 instructions (and each\ninstruction has fixed width of 4 bytes):"}),"\n",(0,s.jsx)(t.p,{children:"MOV dest_reg, src1_reg // Move source register to destination\nregister\nADD dest_reg, src1_reg, src2_reg // Add source1 and source2\nregisters, and store the result in destination register\nBR relative_address // Branch (jump) to relative address (PC +=\nrelative_address * 4)"}),"\n",(0,s.jsx)(t.p,{children:"Processors generally have what we call registers which can hold data,\nand the processor's instructions perform the operations on these\nregisters.\nFor our example, we will assume that our SL3 processor has 8 registers,\nand the size of these registers is 32 bits (so each register holds 32\nbits of data)."}),"\n",(0,s.jsx)(t.p,{children:"Now to program for this processor, we can have the following code:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-cpp",children:"MOV reg1, reg0\nADD reg4, reg2, reg3\nBR 5\n"})}),"\n",(0,s.jsx)(t.p,{children:"What this code does is:"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsx)(t.li,{children:"It moves register 0 to register 1 (so now register 1 holds a copy of\nregister 0's data)."}),"\n",(0,s.jsx)(t.li,{children:"It adds register 2 and register 3 together, and stores the result in\nregister 4."}),"\n",(0,s.jsx)(t.li,{children:"It branches 5 instructions further away (so now it jumps to some code\nthat is further down (not shown in above example))"}),"\n"]}),"\n",(0,s.jsx)(t.p,{children:"So that is how we can program for the SL3 processor in assembly code.\nBut how do we emulate it?"}),"\n",(0,s.jsxs)(t.p,{children:["To actually emulate this processor we can use an ",(0,s.jsx)(t.strong,{children:(0,s.jsx)(t.em,{children:"interpreter"})})," . An\ninterpreter simply fetches each ",(0,s.jsx)(t.em,{children:"instruction opcode"})," and executes them\naccordingly (e.g. by calling emulated methods for each different\ninstruction). The rest of the emulator (emulating other\nprocessors/peripherals of our system) can then get updated sometime in\nbetween instructions or after a group of cpu instructions are run.\nInterpreters are a simple and complete way to emulate a system."]}),"\n",(0,s.jsxs)(t.p,{children:["( ",(0,s.jsx)(t.a,{href:"http://forums.pcsx2.net/thread-13453-post-102002.html#pid102002",children:"Click here to see a C++ code example of a simple\ninterpreter"}),"\n)"]}),"\n",(0,s.jsx)(t.p,{children:"Using interpreters we constantly have to be fetching and executing\ninstructions one-by-one. There is a lot of overhead in this, and minimal\nroom for optimization since most special case optimizations will have\nthe overhead of checking for them (so it will for example add extra\nif-statements and conditionals... reducing the gain from the\noptimization). But there's a faster way to do processor emulation which\ndoesn't have these draw-backs... using dynamic recompilation!"}
1),"\n",(0,s.jsx)(t.p,{children:"The basic idea of dynamic recompilation is to translate emulated\ninstructions once, cache the emitted translated instructions, and then\nrun the emitted native instructions as much times as needed."}),"\n",(0,s.jsx)(t.p,{children:"Since the instructions you read are not changing (lets leave out\nself-modifying code for this example), you can translate the emulated\ninstructions into native cpu instructions (in our case x86-32 asm\ninstructions) and then cache the translated instructions into 'blocks'\nof code, then just execute these blocks of native code instead of having\nto do the translation over and over again whenever the code needs to be\nread again."}),"\n",(0,s.jsx)(t.p,{children:"So for instance remember our above SL3 program:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-cpp",children:"MOV reg1, reg0\nADD reg4, reg2, reg3\nBR 5\n"})}),"\n",(0,s.jsx)(t.p,{children:"Lets assume this code is part of some function and gets called 100's of\ntimes a second (this could sound crazy, but games/applications commonly\ncall the same code hundreds or thousands of times a second)."}),"\n",(0,s.jsx)(t.p,{children:"Now our runCPU() interpreter function above will have to translate every\ninstruction before it can actually compute the result."}),"\n",(0,s.jsx)(t.p,{children:'That is, it needs to fetch the opcode of every instruction, call the\nemulated function based on the opcode number, then actually compute the\ninstruction result.\nBut dynarecs allow us to skip the "fetch opcode" and the "call the\nemulated function" part, by only doing this once, and then caching the\ntranslated code into native asm blocks.'}),"\n",(0,s.jsx)(t.p,{children:"To make a good dynarec, we first need a code emitter.\nAn emitter is a series of functions we call that write native asm to\nsome memory block we give it.\nSo we use an x86-32 emitter to write native x86-32 asm code to blocks of\nmemory, and then later we can execute these blocks as if they were\nnormal c++ generated functions!"}),"\n",(0,s.jsx)(t.p,{children:"PCSX2 has a very cool emitter that looks very similar to x86-32\nassembly, except the instructions have an 'x' before them.\nSo for example:\nmov eax, ecx;\nis\nxMOV(eax, ecx);\nwith the pcsx2 emitter."}),"\n",(0,s.jsx)(t.p,{children:"Now the idea behind the dynarec we are going to write now, is that we\nwill end blocks whenever a branch instruction is detected (which will\njump to other blocks)."}),"\n",(0,s.jsx)(t.p,{children:"The code for actually recompiling these blocks looks something like\nthis:"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-cpp",children:"// This is our emulated MOV instruction\nvoid MOV() {\nu8 dest = fetch(); // Get destination register number\nu8 reg1 = fetch(); // Get source 1 register number\n\nxMOV(eax, ptr[&cpuRegs[reg1]]); // Move reg1's data to eax\nxMOV(ptr[&cpuRegs[dest]], eax); // Move eax to dest register\n\nfetch(); // This fetch is needed because every instruction in our SL3 processor is 4 bytes\n}\n\n// This is our emulated ADD instruction\nvoid ADD() {\nu8 dest = fetch(); // Get destination register number\nu8 reg1 = fetch(); // Get source 1 register number\nu8 reg2 = fetch(); // Get source 2 register number\n\nxMOV(eax, ptr[&cpuRegs[reg1]]); // Move reg1's data to eax\nxADD(eax, ptr[&cpuRegs[reg2]]); // Add eax with reg2's data\nxMOV(ptr[&cpuRegs[dest]], eax); // Move eax to dest register\n}\n\n// This is our emulated BR (jump) instruction\nvoid BR() {\ns8 addr = fetch(); // Gets a number by which we will increment (or decrement if negative) PC by\nPC = (PC - 2) + (addr * 4);\n\n// Get a pointer to a block of x86-32 compiled code\n// that was recompiled by the recompileSL3() function\nu8* block_pointer = getBlock(PC);\n\nxJMP(block_pointer); // Jump to the pointer returned by getBlock()\n}\n\n// This goes through instructions and recompiles them\n// It recompiles instructions until it reaches a BR() instruction.\nu8* recompileSL3(u32 startPC) {\nu8* startPtr = xGetPtr(); // Gets pointer to where the emitter is currently pointing to (the start pointer of the block)\nPC = startPC; // Set PC to the start PC of this block\nbool do_recompile = true;\nwhile (do_recompile) {\nu8 opcode = fetch();\nswitch (opcode) {\ncase 0: MOV(); break;\ncase 1: ADD(); break;\ncase 2: // We stop recompiling on branches\nBR();\ndo_recompile = false;\nbreak;\n}\n}\nreturn startPtr; // Returns the pointer to where our block of x86 generated code starts at\n}\n\n// This holds all the pointers to our blocks that were recompiled based on\n// starting PC address. We will assume that the instruction memory for\n// this processor is 16kb, which means that it can hold at-most 1024*16 bytes\n// worth of instructions. And therefor we we have at-most 1024*16 block pointers.\nstatic u8* blockArray[1024*16];\n\n// This returns a pointer to our recompiled block\n// If it hasn't been compiled, it'll recompile the block and then return that pointer.\n// We use __fastcall because it lets us pass the startPC parameter in the ecx register\n// instead of having to use the x86 stack...\nu8* __fastcall getBlock(u32 startPC) {\nif (blockArray[startPC] == null) {\nblockArray[startPC] = recompileSL3(startPC);\n}\nreturn blockArray[startPC];\n}\n\n// Basic cpu emulator using dynamic recompilation\nvoid runCPU() {\n// This sets our emitter to start emitting instructions to rec_cache\n// which is a large block of memory where we can write lots of\n// x86 asm instructions to...\nx86setPtr(rec_cache);\n\n__asm {\npushad; // Save all our registers\nmov ecx, PC; // Move PC parameter into ecx register (for __fastcall)\ncall getBlock; // Call the getBlock function\njmp eax; // The block to jump to is returned in eax\n}\n}\n"})}),"\n",(0,s.jsxs)(t.p,{children:["Note the above code doesn't have any logic to successfully exit once it\nstarts executing recompiled blocks... I left this stuff out in order to\nnot c
1omplicate things... so assume that somehow execution ends and we\ncan get back to running the other parts of the emulator...\nAlso note we need to restore registers when we exit execution, and we\nalso need to set rec_cache to the new x86 emitter address (so it can\ncontinue where it left off instead of writing over already recompiled\nmemory blocks).\n( ",(0,s.jsx)(t.a,{href:"http://forums.pcsx2.net/Thread-blog-Introduction-to-Dynamic-Recompilation?pid=102471#pid102471",children:"Click here for a more complete sl3 recompiler code with cycle\ncounting and exit\nlogic"}),"\n)"]}),"\n",(0,s.jsx)(t.p,{children:"Now how does this work?\nWell we basically call runCPU() which calls the getBlock() function with\nthe original PC value.\ngetBlock() then checks if we have already recompiled a block with that\nstart PC value, which we havn't yet, so it will go on to call\nrecompileSL3() and give it the startPC value.\nrecompileSL3() will loop through the opcodes, fetching them and then\ncalling the appropriate emulated functions which will write to memory\nx86-32 asm instructions computing the correct results.\nrecompileSL3() will continue looping until it reaches a BR() instruction\n(which will recursively call getBlock() until all the addresses have\nbeen recompiled and no more recompilation needs to happen)."}),"\n",(0,s.jsx)(t.p,{children:"Once everything has been recompiled we jump to the starting block's\nexecution address, and that's how we actually run the execution.\nThe code that ends up being executed after we recompiled is only the\nemitted code, which were the functions prefixed with 'x' (xMOV, xADD,\netc...).\nNotice that that's a lot of code omitted as we don't have to fetch\nanything anymore, but instead just run a series of fast generated asm\nfunctions...\nThis means that although we have a lot of extra overhead on the actual\nrecompilation, we end up generating some really fast code, that could be\ncalled 1000's of times a second, therefor making the initial overhead\nwell worth it!"}),"\n",(0,s.jsx)(t.p,{children:"We can also perform many optimizations while recompiling such as\nregalloc, constant propagation, optimized asm generation for special\nopcode cases, etc... (we didn't do this stuff in our example to avoid\ncomplexities)."}),"\n",(0,s.jsx)(t.p,{children:"Hopefully this article has helped you gain a bit of understanding on how\ndynamic recompilation works, and why we use it in pcsx2 to gain a lot\nmore speed over interpreters!\nAlso note that dynarecs can be used for more than processor emulation,\nwe also used dynamic recompilation in pcsx2 to cache asm optimized\nunpack routines that the ps2's vif does."}),"\n",(0,s.jsx)(t.p,{children:"I should also add that currently pcsx2 has the following dynarecs:\nEE recompiler (for MIPS R5900 ee-core processor)\nIOP recompiler (for MIPS R3000A i/o processor)\nmicroVU recompiler (for VU0/VU1, and COP2 instructions)\nSuper VU recompiler (can be used instead of microVU for VU0/VU1)\nnewVIF unpack recompiler (recompiles vif unpack routines)\nr3000air (not yet finished, but should one day supersede the IOP\nrecompiler)"})]})}function d(e={}){let{wrapper:t}={...(0,r.a)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},50065:function(e,t,n){n.d(t,{Z:()=>a,a:()=>i});var o=n(67294);let s={},r=o.createContext(s);function i(e){let t=o.useContext(r);return o.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),o.createElement(r.Provider,{value:t},e.children)}},17791:function(e){e.exports=JSON.parse('{"permalink":"/blog/2010/introduction-to-dynamic-recompilation","editUrl":"https://github.com/PCSX2/pcsx2-net-www/tree/main/blog/2010/introduction-to-dynamic-recompilation/index.mdx","source":"@site/blog/2010/introduction-to-dynamic-recompilation/index.mdx","title":"Introduction To Dynamic Recompilation","description":"his blog post is an introduction to dynamic recompilers (dynarecs), and hopes to provide some insight on how they work and why pcsx2 uses them to speed up emulation","date":"2010-03-13T00:00:00.000Z","tags":[{"inline":true,"label":"devblog","permalink":"/blog/tags/devblog"}],"readingTime":9.22,"hasTruncateMarker":true,"authors":[{"name":"cottonvibes","key":"cottonvibes","page":null}],"frontMatter":{"authors":["cottonvibes"],"date":"2010-03-13T00:00:00.000Z","description":"his blog post is an introduction to dynamic recompilers (dynarecs), and hopes to provide some insight on how they work and why pcsx2 uses them to speed up emulation","draft":false,"tags":["devblog"],"title":"Introduction To Dynamic Recompilation"},"unlisted":false,"prevItem":{"title":"Updated Plugins On The Beta Downloads Page","permalink":"/blog/2010/updated-plugins-on-the-beta-downloads-page"},"nextItem":{"title":"PCSX2 0.9.7 Sneak Peek!","permalink":"/blog/2010/pcsx2-0-9-7-sneak-peek"}}')}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.