時(shí)間:2023-06-29 20:21:02 | 來源:網(wǎng)站運(yùn)營(yíng)
時(shí)間:2023-06-29 20:21:02 來源:網(wǎng)站運(yùn)營(yíng)
400 行 C 代碼實(shí)現(xiàn)一個(gè)虛擬機(jī):↓推薦關(guān)注↓注意:這個(gè)虛擬機(jī)是Literate Programming 的產(chǎn)物。本文會(huì)解釋每段代碼的原理,最終的實(shí)現(xiàn)就是將這些代碼片段連起來。
注:編譯器也解決了類似的跨平臺(tái)問題,它將標(biāo)準(zhǔn)的高級(jí)語言編寫的程序編譯成能在不同 CPU 架構(gòu)上執(zhí)行的程序。Java Virtual Machine (JVM) 就是一個(gè)非常成功的例子。JVM 本身是一個(gè)中等大小、程序員完全能夠看懂的程序,因此很 容易將它移植到包括手機(jī)在內(nèi)的上千種設(shè)備上。只要在設(shè)備上實(shí)現(xiàn)了 JVM,接下來任何 Java、Kotlin 或 Clojure 程序都無需任何修改就可以直接運(yùn)行在這個(gè)設(shè)備上。唯一的開銷 來自虛擬機(jī)自身以及機(jī)器之上的 進(jìn)一步抽象。大部分情況下,這完全是可以接受的。
相比之下,虛擬機(jī)的跨平臺(tái)方式是自己創(chuàng)建一個(gè)標(biāo)準(zhǔn)的 CPU 架 構(gòu),然后在不同的物理設(shè)備上模擬這個(gè) CPU 架構(gòu)。編譯器方式的優(yōu)點(diǎn)是沒有運(yùn)行時(shí)開銷 (runtime overhead),但實(shí)現(xiàn)一個(gè)支持多平臺(tái)的編譯器是非常困難的,但實(shí)現(xiàn)一個(gè)虛擬 機(jī)就簡(jiǎn)單多了。
在實(shí)際中,人們會(huì)根據(jù)需求的不同混合使用虛擬機(jī)和編譯器,因?yàn)槎吖?作在不同的層次。
/*65536locations*/uint16_tmemory[UINT16_MAX];
enum{R_R0=0,R_R1,R_R2,R_R3,R_R4,R_R5,R_R6,R_R7,R_PC,/*programcounter*/R_COND,R_COUNT};
和內(nèi)存一樣,我們也用數(shù)組來表示這些寄存器:uint16_treg[R_COUNT];
enum{OP_BR=0,/*branch*/OP_ADD,/*add*/OP_LD,/*load*/OP_ST,/*store*/OP_JSR,/*jumpregister*/OP_AND,/*bitwiseand*/OP_LDR,/*loadregister*/OP_STR,/*storeregister*/OP_RTI,/*unused*/OP_NOT,/*bitwisenot*/OP_LDI,/*loadindirect*/OP_STI,/*storeindirect*/OP_JMP,/*jump*/OP_RES,/*reserved(unused)*/OP_LEA,/*loadeffectiveaddress*/OP_TRAP/*executetrap*/};
注:Intel x86 架構(gòu)有幾百條指令,而其他的架構(gòu)例如 ARM 和 LC-3 只有很少的指令 。較小的指令集稱為精簡(jiǎn)指令集(RISC),較大 的指令集稱為復(fù)雜指令集(CISC)。更大 的指令集本質(zhì)上通常并沒有提供新特性,只是使得編寫 匯編更加方便 。一條 CISC 指令能做的事情可能需要好幾條 RISC 才能完成。
但是,對(duì)設(shè)計(jì)和制造工程 師來說,CISC 更加復(fù)雜和昂貴,設(shè)計(jì)和制造業(yè)更貴。包括這一點(diǎn)在內(nèi)的一些權(quán)衡使得指 令設(shè)計(jì)也在不斷變化。
if (x > 0) { ... }
之類的邏輯條件。enum{FL_POS=1<<0,/*P*/FL_ZRO=1<<1,/*Z*/FL_NEG=1<<2,/*N*/};
注:<< 和 >> 表示移位操作。.ORIG x3000 ; this is the address in memory where the program will be loadedLEA R0, HELLO_STR ; load the address of the HELLO_STR string into R0PUTs ; output the string pointed to by R0 to the consoleHALT ; halt the programHELLO_STR .STRINGZ "Hello World!" ; store this string here in the program.END ; mark the end of the file
和 C 類似,這段程序從最上面開始,每次執(zhí)行一條聲明(statement)。但和 C 不同的是, 這里沒有作用域符號(hào) {} 或者控制結(jié)構(gòu)(例如 if 和 while),僅僅是一個(gè)扁平的聲 明列表(a flat list of statements)。這樣的程序更容易執(zhí)行。注:雖然在開發(fā)中編譯器(compiler)和匯編器(assembler)的角色是類似的,但二者 是兩個(gè)不同的工具。匯編器只是簡(jiǎn)單地將程序員編寫的文本編碼(encode)成二進(jìn)制格式 ,將其中的符號(hào)替換成相應(yīng)的二進(jìn)制表示并打包到指令內(nèi)。
.ORIG
和.STRINGZ
看起來像是指令,但其實(shí)不是,它們稱為匯編制導(dǎo)命令 (assembler directives),可以生成一段代碼或數(shù)據(jù)。例如,.STRINGZ
會(huì)在它所在的 位置插入一段字符串。AND R0, R0, 0 ; clear R0LOOP ; label at the top of our loopADD R0, R0, 1 ; add 1 to R0 and store back in R0ADD R1, R0, -10 ; subtract 10 from R0 and store back in R1BRn LOOP ; go back to LOOP if the result was negative... ; R0 is now 10!
注:本文不需要讀者會(huì)編寫匯編代碼。但如果你感興趣,你可以使用 LC-3 工具來編寫和匯編你自己寫的匯編程序。
“The view that machines cannot give rise to surprises is due, I believe, to a fallacy to which philosophers and mathematicians are particularly subject. This is the assumption that as soon as a fact is presented to a mind all consequences of that fact spring into the mind simultaneously with it. It is a very useful assumption under many circumstances, but one too easily forgets that it is false.” — Alan M. Turing
intmain(intargc,constchar*argv[]){{LoadArguments,12}{Setup,12}/*setthePCtostartingposition*/enum{PC_START=0x3000};/*0x3000isthedefault*/reg[R_PC]=PC_START;intrunning=1;while(running){uint16_tinstr=mem_read(reg[R_PC]++);/*FETCH*/uint16_top=instr>>12;switch(op){caseOP_ADD:{ADD,6}break;caseOP_AND:{AND,7}break;caseOP_NOT:{NOT,7}break;caseOP_BR:{BR,7}break;caseOP_JMP:{JMP,7}break;caseOP_JSR:{JSR,7}break;caseOP_LD:{LD,7}break;caseOP_LDI:{LDI,6}break;caseOP_LDR:{LDR,7}break;caseOP_LEA:{LEA,7}break;caseOP_ST:{ST,7}break;caseOP_STI:{STI,7}break;caseOP_STR:{STR,7}break;caseOP_TRAP:{TRAP,8}break;caseOP_RES:caseOP_RTI:default:{BADOPCODE,7}break;}}{Shutdown,12}}
ADD R2 R0 R1 ; add the contents of R0 to R1 and store in R2.
在立即模式中,第二個(gè)數(shù)直接存儲(chǔ)在指令中,而不是寄存器中。這種模式更加方便,因 為程序不需要額外的指令來將數(shù)據(jù)從內(nèi)存加載到寄存器,直接從指令中就可以拿到這個(gè)值。這種方式的限制是存儲(chǔ)的數(shù)很小,不超過 2^5 = 32(無符號(hào))。這種方式很適合對(duì)一個(gè)值 進(jìn)行遞增。用匯編描述就是:ADD R0 R0 1 ; add 1 to R0 and store back in R0
下面一段解釋來自 LC-3 規(guī)范:If bit [5] is 0, the second source operand is obtained from SR2. If bit [5] is 1, the second source operand is obtained by sign-extending the imm5 field to 16 bits. In both cases, the second source operand is added to the contents of SR1 and the result stored in DR. (Pg. 526)這段解釋也就是我們前面討論的內(nèi)容。但什么是 “sign-extending”(有符號(hào)擴(kuò)展)?雖然立即 模式中存儲(chǔ)的值只有 5 比特,但這個(gè)值需要加到一個(gè) 16 比特的值上。因此,這些 5 比 特的數(shù)需要擴(kuò)展到 16 比特才能和另一個(gè)數(shù)相匹配。對(duì)于正數(shù),我們可以在前面填充 0, 填充之后值是不變的。但是,對(duì)于負(fù)數(shù),這樣填充會(huì)導(dǎo)致問題。例如, -1 的 5 比特表示 是 11111。如果我們用 0 填充,那填充之后的 0000 0000 0001 1111 等于 32!這種 情況下就需要使用有符號(hào)擴(kuò)展( sign extension),對(duì)于正數(shù)填充 0,對(duì)負(fù)數(shù)填充 1。
uint16_tsign_extend(uint16_tx,intbit_count){if((x>>(bit_count-1))&1){x|=(0xFFFF<<bit_count);}returnx;}
注:如果你如何用二進(jìn)制表示負(fù)數(shù)感興趣,可以查閱二進(jìn)制補(bǔ)碼(Two’s Complement) 相關(guān)的內(nèi)容。本文中只需要知道怎么進(jìn)行有符號(hào)擴(kuò)展就行了。規(guī)范中還有一句:
The condition codes are set, based on whether the result is negative, zero, or positive. (Pg. 526)前面我們定義的那個(gè)條件標(biāo)記枚舉類型現(xiàn)在要派上用場(chǎng)了。每次有值寫到寄存器時(shí),我們 需要更新這個(gè)標(biāo)記,以標(biāo)明這個(gè)值的符號(hào)。為了方便,我們用下面的函數(shù)來實(shí)現(xiàn)這個(gè)功能:
voidupdate_flags(uint16_tr){if(reg[r]==0){reg[R_COND]=FL_ZRO;}elseif(reg[r]>>15){/*a1intheleft-mostbitindicatesnegative*/reg[R_COND]=FL_NEG;}else{reg[R_COND]=FL_POS;}}
現(xiàn)在我們就可以實(shí)現(xiàn) ADD 的邏輯了:{uint16_tr0=(instr>>9)&0x7;/*destinationregister(DR)*/uint16_tr1=(instr>>6)&0x7;/*firstoperand(SR1)*/uint16_timm_flag=(instr>>5)&0x1;/*whetherweareinimmediatemode*/if(imm_flag){uint16_timm5=sign_extend(instr&0x1F,5);reg[r0]=reg[r1]+imm5;}else{uint16_tr2=instr&0x7;reg[r0]=reg[r1]+reg[r2];}update_flags(r0);}
本節(jié)包含了大量信息,這里再總結(jié)一下:An address is computed by sign-extending bits [8:0] to 16 bits and adding this value to the incremented PC. What is stored in memory at this address is the address of the data to be loaded into DR. (Pg. 532)和前面一樣,我們需要將這個(gè) 9 比特的 PCoffset9 以有符號(hào)的方式擴(kuò)展到 16 比特,但 這次是將擴(kuò)展之后的值加到當(dāng)前的程序計(jì)數(shù)器 PC(如果回頭去看前面的 while 循 環(huán),就會(huì)發(fā)現(xiàn)這條指令加載之后 PC 就會(huì)遞增)。相加得到的結(jié)果(也就是 PC 加完之后的 值)表示一個(gè)內(nèi)存地址,這個(gè)地址中存儲(chǔ)的值表示另一個(gè)地址,后者中存儲(chǔ)的是需要加載到 DR 中的值。
//thevalueoffar_dataisanaddress//ofcoursefar_dataitself(thelocationinmemorycontainingtheaddress)hasanaddresschar*far_data="apple";//Inmemoryitmaybelayedoutlikethis://AddressLabelValue//0x123:far_data=0x456//...//0x456:string='a'//ifPCwasat0x100//LDIR00x023//wouldload'a'intoR0
和 ADD 類似,將值放到 DR 之后需要更新條件標(biāo)志位:The condition codes are set based on whether the value loaded is negative, zero, or positive. (Pg. 532)下面是我對(duì) LDI 的實(shí)現(xiàn)(后面章節(jié)中會(huì)介紹 mem_read):
{uint16_tr0=(instr>>9)&0x7;/*destinationregister(DR)*/uint16_tpc_offset=sign_extend(instr&0x1ff,9);/*PCoffset9*//*addpc_offsettothecurrentPC,lookatthatmemorylocationtogetthefinaladdress*/reg[r0]=mem_read(mem_read(reg[R_PC]+pc_offset));update_flags(r0);}
后面會(huì)看到,這些指令的實(shí)現(xiàn)中,大部分輔助功能函數(shù)都是可以復(fù)用的。abort();
{uint16_tr0=(instr>>9)&0x7;uint16_tr1=(instr>>6)&0x7;uint16_timm_flag=(instr>>5)&0x1;if(imm_flag){uint16_timm5=sign_extend(instr&0x1F,5);reg[r0]=reg[r1]&imm5;}else{uint16_tr2=instr&0x7;reg[r0]=reg[r1]®[r2];}update_flags(r0);}
{uint16_tr0=(instr>>9)&0x7;uint16_tr1=(instr>>6)&0x7;reg[r0]=~reg[r1];update_flags(r0);}
{uint16_tpc_offset=sign_extend((instr)&0x1ff,9);uint16_tcond_flag=(instr>>9)&0x7;if(cond_flag®[R_COND]){reg[R_PC]+=pc_offset;}}
{/*AlsohandlesRET*/uint16_tr1=(instr>>6)&0x7;reg[R_PC]=reg[r1];}
{uint16_tr1=(instr>>6)&0x7;uint16_tlong_pc_offset=sign_extend(instr&0x7ff,11);uint16_tlong_flag=(instr>>11)&1;reg[R_R7]=reg[R_PC];if(long_flag){reg[R_PC]+=long_pc_offset;/*JSR*/}else{reg[R_PC]=reg[r1];/*JSRR*/}break;}
{uint16_tr0=(instr>>9)&0x7;uint16_tpc_offset=sign_extend(instr&0x1ff,9);reg[r0]=mem_read(reg[R_PC]+pc_offset);update_flags(r0);}
{uint16_tr0=(instr>>9)&0x7;uint16_tr1=(instr>>6)&0x7;uint16_toffset=sign_extend(instr&0x3F,6);reg[r0]=mem_read(reg[r1]+offset);update_flags(r0);}
{uint16_tr0=(instr>>9)&0x7;uint16_tpc_offset=sign_extend(instr&0x1ff,9);reg[r0]=reg[R_PC]+pc_offset;update_flags(r0);}
{uint16_tr0=(instr>>9)&0x7;uint16_tpc_offset=sign_extend(instr&0x1ff,9);mem_write(reg[R_PC]+pc_offset,reg[r0]);}
{uint16_tr0=(instr>>9)&0x7;uint16_tpc_offset=sign_extend(instr&0x1ff,9);mem_write(mem_read(reg[R_PC]+pc_offset),reg[r0]);}
{uint16_tr0=(instr>>9)&0x7;uint16_tr1=(instr>>6)&0x7;uint16_toffset=sign_extend(instr&0x3F,6);mem_write(reg[r1]+offset,reg[r0]);}
enum{TRAP_GETC=0x20,/*getcharacterfromkeyboard,notechoedontotheterminal*/TRAP_OUT=0x21,/*outputacharacter*/TRAP_PUTS=0x22,/*outputawordstring*/TRAP_IN=0x23,/*getcharacterfromkeyboard,echoedontotheterminal*/TRAP_PUTSP=0x24,/*outputabytestring*/TRAP_HALT=0x25/*halttheprogram*/};
你可能會(huì)覺得奇怪:為什么 trap code 沒有包含在指令編碼中?這是因?yàn)樗鼈儧]有給 LC-3 帶來任何新功能,只是提供了一種方便地執(zhí)行任務(wù)的方式(和 C 中的系統(tǒng)函數(shù)類似 )。在官方 LC-3 模擬器中,trap routines 是用匯編實(shí)現(xiàn)的。當(dāng)調(diào)用到 trap code 時(shí),PC 會(huì)移動(dòng)到 code 對(duì)應(yīng)的地址。CPU 執(zhí)行這個(gè)函數(shù)( procedure)的指令流,函數(shù)結(jié)束后 PC 重置到 trap 調(diào)用之前的位置。注:這就是為什么程序從 0x3000 而不是 0x0 開始的原因。低地址空間是特意留出來 給 trap routine 用的。規(guī)范只定義了 trap routine 的行為,并沒有規(guī)定應(yīng)該如何實(shí)現(xiàn)。在我們這個(gè)虛擬機(jī)中, 將會(huì)用 C 實(shí)現(xiàn)。當(dāng)觸發(fā)某個(gè) trap code 時(shí),會(huì)調(diào)用一個(gè)相應(yīng)的 C 函數(shù)。這個(gè)函數(shù)執(zhí)行 完成后,執(zhí)行過程會(huì)返回到原來的指令流。
注:從鍵盤獲取輸入就是一個(gè)例子。匯編版本使用一個(gè)循環(huán)來持續(xù)檢查鍵盤有沒有輸入 ,這會(huì)消耗大量 CPU 而實(shí)際上沒做多少事情!使用操作系統(tǒng)提供的某個(gè)合適的輸入函 數(shù)的話,程序可以在收到輸入之前一直 sleep。TRAP 處理邏輯:
switch(instr&0xFF){caseTRAP_GETC:{TRAPGETC,9}break;caseTRAP_OUT:{TRAPOUT,9}break;caseTRAP_PUTS:{TRAPPUTS,8}break;caseTRAP_IN:{TRAPIN,9}break;caseTRAP_PUTSP:{TRAPPUTSP,9}break;caseTRAP_HALT:{TRAPHALT,9}break;}
和前面幾節(jié)類似,我會(huì)拿一個(gè) trap routine 作為例子展示如何實(shí)現(xiàn),其他的留給讀者自己 完成。Write a string of ASCII characters to the console display. The characters are contained in consecutive memory locations, one character per memory location, starting with the address specified in R0. Writing terminates with the occurrence of x0000 in a memory location. (Pg. 543)意思是說字符串是存儲(chǔ)在一個(gè)連續(xù)的內(nèi)存區(qū)域。注意這里和 C 中的字符串有所不同:C 中每個(gè)字符占用一個(gè) byte;LC-3 中內(nèi)存尋找是 16 位的,每個(gè)字符都是 16 位,占用 兩個(gè) byte。因此要用 C 函數(shù)打印這些字符,需要將每個(gè)值先轉(zhuǎn)換成 char 類型再輸出:
{/*onecharperword*/uint16_t*c=memory+reg[R_R0];while(*c){putc((char)*c,stdout);++c;}fflush(stdout);}
這就是 PUTS trap routine 的實(shí)現(xiàn)了。如果熟悉 C 的話,這個(gè)函數(shù)應(yīng)該很容易理解。現(xiàn) 在你可以按照 LC-3 規(guī)范,自己動(dòng)手實(shí)現(xiàn)其他的 trap routine 了。/*readasingleASCIIchar*/reg[R_R0]=(uint16_t)getchar();
putc((char)reg[R_R0],stdout);fflush(stdout);
printf("Enteracharacter:");charc=getchar();putc(c,stdout);reg[R_R0]=(uint16_t)c;
{/*onecharperbyte(twobytesperword)hereweneedtoswapbacktobigendianformat*/uint16_t*c=memory+reg[R_R0];while(*c){charchar1=(*c)&0xFF;putc(char1,stdout);charchar2=(*c)>>8;if(char2)putc(char2,stdout);++c;}fflush(stdout);}
puts("HALT");fflush(stdout);running=0;
voidread_image_file(FILE*file){uint16_torigin;/*theorigintellsuswhereinmemorytoplacetheimage*/fread(&origin,sizeof(origin),1,file);origin=swap16(origin);/*weknowthemaximumfilesizesoweonlyneedonefread*/uint16_tmax_read=UINT16_MAX-origin;uint16_t*p=memory+origin;size_tread=fread(p,sizeof(uint16_t),max_read,file);/*swaptolittleendian*/while(read-->0){*p=swap16(*p);++p;}}
注意讀取前 16 比特之后,對(duì)這個(gè)值執(zhí)行了 swap16()。這是因?yàn)?LC-3 程序是大端 (big-endian),但現(xiàn)在大部分計(jì)算機(jī)都是小端的(little-endian),因此需要做大小端 轉(zhuǎn)換。如果你是在某些特殊的機(jī)器 (例如 PPC)上運(yùn)行,那就不 需要這些轉(zhuǎn)換了。uint16_tswap16(uint16_tx){return(x<<8)|(x>>8);}
注:大小端(Endianness)是指對(duì)于 一個(gè)整型數(shù)據(jù),它的每個(gè)字節(jié)應(yīng)該如何解釋。在小端中,第一個(gè)字節(jié)是最低位,而在大端 中剛好相反,第一個(gè)字節(jié)是最高位。據(jù)我所知,這個(gè)順序完全是人為規(guī)定的。不同的公司 做出的抉擇不同,因此我們這些后來人只能針對(duì)大小端做一些特殊處理。要理解本文中大 小端相關(guān)的內(nèi)容,知道這些就足夠了。我們?cè)俜庋b一下前面加載程序的函數(shù),接受一個(gè)文件路徑字符串作為參數(shù),這樣更加方便:
intread_image(constchar*image_path){FILE*file=fopen(image_path,"rb");if(!file){return0;};read_image_file(file);fclose(file);return1;}
enum{MR_KBSR=0xFE00,/*keyboardstatus*/MR_KBDR=0xFE02/*keyboarddata*/};
內(nèi)存映射寄存器使內(nèi)存訪問稍微復(fù)雜了一些。這種情況下不能直接讀寫內(nèi)存位置,而要使 用 setter 和 getter 輔助函數(shù)。當(dāng)獲取輸入時(shí),getter 會(huì)檢查鍵盤輸入并更新兩 個(gè)寄存器(也就是相應(yīng)的內(nèi)存位置)。voidmem_write(uint16_taddress,uint16_tval){memory[address]=val;}uint16_tmem_read(uint16_taddress){if(address==MR_KBSR){if(check_key()){memory[MR_KBSR]=(1<<15);memory[MR_KBDR]=getchar();}else{memory[MR_KBSR]=0;}}returnmemory[address];}
這就是我們的虛擬機(jī)的最后一部分了!只要你實(shí)現(xiàn)了前面提到的 trap routine 和指令,你 的虛擬機(jī)就即將能夠運(yùn)行了!uint16_tcheck_key(){fd_setreadfds;FD_ZERO(&readfds);FD_SET(STDIN_FILENO,&readfds);structtimevaltimeout;timeout.tv_sec=0;timeout.tv_usec=0;returnselect(1,&readfds,NULL,NULL,&timeout)!=0;}
下面是特定于 Unix 的設(shè)置終端輸入的代碼:structtermiosoriginal_tio;voiddisable_input_buffering(){tcgetattr(STDIN_FILENO,&original_tio);structtermiosnew_tio=original_tio;new_tio.c_lflag&=~ICANON&~ECHO;tcsetattr(STDIN_FILENO,TCSANOW,&new_tio);}voidrestore_input_buffering(){tcsetattr(STDIN_FILENO,TCSANOW,&original_tio);}
當(dāng)程序被中斷時(shí),我們需要將終端的設(shè)置恢復(fù)到默認(rèn):voidhandle_interrupt(intsignal){restore_input_buffering();printf("/n");exit(-2);}signal(SIGINT,handle_interrupt);disable_input_buffering();
Play 2048!{2048 Example 13}Control the game using WASD keys.Are you on an ANSI terminal (y/n)? y+--------------------------+| || || || 2 || || 2 || || || |+--------------------------+
{InstructionC++14}template<unsignedop>voidins(uint16_tinstr){uint16_tr0,r1,r2,imm5,imm_flag;uint16_tpc_plus_off,base_plus_off;uint16_topbit=(1<<op);if(0x4EEE&opbit){r0=(instr>>9)&0x7;}if(0x12E3&opbit){r1=(instr>>6)&0x7;}if(0x0022&opbit){r2=instr&0x7;imm_flag=(instr>>5)&0x1;imm5=sign_extend((instr)&0x1F,5);}if(0x00C0&opbit){//Base+offsetbase_plus_off=reg[r1]+sign_extend(instr&0x3f,6);}if(0x4C0D&opbit){//Indirectaddresspc_plus_off=reg[R_PC]+sign_extend(instr&0x1ff,9);}if(0x0001&opbit){//BRuint16_tcond=(instr>>9)&0x7;if(cond®[R_COND]){reg[R_PC]=pc_plus_off;}}if(0x0002&opbit){//ADDif(imm_flag){reg[r0]=reg[r1]+imm5;}else{reg[r0]=reg[r1]+reg[r2];}}if(0x0020&opbit){//ANDif(imm_flag){reg[r0]=reg[r1]&imm5;}else{reg[r0]=reg[r1]®[r2];}}if(0x0200&opbit){reg[r0]=~reg[r1];}//NOTif(0x1000&opbit){reg[R_PC]=reg[r1];}//JMPif(0x0010&opbit){//JSRuint16_tlong_flag=(instr>>11)&1;pc_plus_off=reg[R_PC]+sign_extend(instr&0x7ff,11);reg[R_R7]=reg[R_PC];if(long_flag){reg[R_PC]=pc_plus_off;}else{reg[R_PC]=reg[r1];}}if(0x0004&opbit){reg[r0]=mem_read(pc_plus_off);}//LDif(0x0400&opbit){reg[r0]=mem_read(mem_read(pc_plus_off));}//LDIif(0x0040&opbit){reg[r0]=mem_read(base_plus_off);}//LDRif(0x4000&opbit){reg[r0]=pc_plus_off;}//LEAif(0x0008&opbit){mem_write(pc_plus_off,reg[r0]);}//STif(0x0800&opbit){mem_write(mem_read(pc_plus_off),reg[r0]);}//STIif(0x0080&opbit){mem_write(base_plus_off,reg[r0]);}//STRif(0x8000&opbit){//TRAP{TRAP,8}}//if(0x0100&opbit){}//RTIif(0x4666&opbit){update_flags(r0);}}{OpTable14}staticvoid(*op_table[16])(uint16_t)={ins<0>,ins<1>,ins<2>,ins<3>,ins<4>,ins<5>,ins<6>,ins<7>,NULL,ins<9>,ins<10>,ins<11>,ins<12>,NULL,ins<14>,ins<15>};
這里的技巧是從 Bisqwit’s NES emulator 學(xué)來的。如果你對(duì)仿真或 NES 感興趣,強(qiáng)烈建議觀看他的視頻。譯者:ArthurChiao- EOF -
來源:https://arthurchiao.art/blog/write-your-own-virtual-machine-zh/
原文鏈接:
https://justinmeiners.github.io/lc3-vm/
關(guān)鍵詞:虛擬,實(shí)現(xiàn)
客戶&案例
營(yíng)銷資訊
關(guān)于我們
客戶&案例
營(yíng)銷資訊
關(guān)于我們
微信公眾號(hào)
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。