過去 18 年間、私は C / C ++ で書く必要がなかったことがたまたまありました。仕事では Java が使用され、その職位により、活動は起業家精神に関連していました。つまり、交渉、法人営業、生産業務の構築、投資取引の構築などです。仕事から離れた時間にスキルを回復し、18 年間緊張しなかった脳の部分をストレッチし、当然のことながら、非常に基本的なところから始めたいと思っていました。それは課題を考え出すために残っています。
, 70-80 , - (, ) . "", (Go, Kotlin ) .
32-bit C , . Computer Science . , , , . . . .
, :
CPU: 32-bit , , , IP (Instruction Pointer) SP (Stack Pointer), (__int32), .
RAM: 65536 32-bit`. . (code/text) (data, heap), (stack). .
:
typedef __int32 WORD;
constexpr WORD OP_CODE_MASK = 0b00000000000000000000000011111111;
constexpr WORD OP_TYPE_MASK = 0b00000000000000000000111000000000;
constexpr WORD OP_HALT = 0b00000000000000000000000000000000;
constexpr WORD OP_CONST = 0b00000000000000000000000000000001;
constexpr WORD OP_PUSH = 0b00000000000000000000000000000010;
constexpr WORD OP_POP = 0b00000000000000000000000000000011;
constexpr WORD OP_INC = 0b00000000000000000000000000000100;
constexpr WORD OP_DEC = 0b00000000000000000000000000000101;
constexpr WORD OP_ADD = 0b00000000000000000000000000000110;
constexpr WORD OP_SUB = 0b00000000000000000000000000000111;
constexpr WORD OP_MUL = 0b00000000000000000000000000001000;
constexpr WORD OP_DIV = 0b00000000000000000000000000001001;
constexpr WORD OP_AND = 0b00000000000000000000000000001010;
constexpr WORD OP_OR = 0b00000000000000000000000000001011;
constexpr WORD OP_XOR = 0b00000000000000000000000000001100;
constexpr WORD OP_NOT = 0b00000000000000000000000000001101;
constexpr WORD OP_SHL = 0b00000000000000000000000000001110;
constexpr WORD OP_SHR = 0b00000000000000000000000000001111;
constexpr WORD OP_JMP = 0b00000000000000000000000000010001;
constexpr WORD OP_CMPJE = 0b00000000000000000000000000010010;
constexpr WORD OP_CMPJNE = 0b00000000000000000000000000010011;
constexpr WORD OP_CMPJG = 0b00000000000000000000000000010100;
constexpr WORD OP_CMPJGE = 0b00000000000000000000000000010101;
constexpr WORD OP_CMPJL = 0b00000000000000000000000000010110;
constexpr WORD OP_CMPJLE = 0b00000000000000000000000000010111;
constexpr WORD OP_DUP = 0b00000000000000000000000000011000;
constexpr WORD OP_CALL = 0b00000000000000000000000000011001;
constexpr WORD OP_RET = 0b00000000000000000000000000011010;
constexpr WORD OP_SYSCALL = 0b00000000000000000000000000011011;
constexpr WORD OP_RESERVED1 = 0b00000000000000000000000000011100;
constexpr WORD OP_RESERVED2 = 0b00000000000000000000000000011101;
constexpr WORD OP_RESERVED3 = 0b00000000000000000000000000011110;
constexpr WORD OP_RESERVED4 = 0b00000000000000000000000000011111;
constexpr WORD MAX_MEMORY = 65536;
8 32 (opcode), 1 (immediate ), 3 (byte, short, int, long, char, float, double ), . .
class VMRuntime {
public:
VMRuntime(); // Constructor
~VMRuntime(); // Desctructor
bool loadImage(void* image, size_t size); // Load executable image
void run(); // Runs image from address 0
WORD readWord(WORD address); // Read WORD from memory
void writeWord(WORD address, WORD value); // Write WORD to memory
WORD getMaxAddress(); // Get max address in 32-bit words
WORD getIP(); // Get Instruction Pointer address
WORD getSP(); // Get Stack Pointer address
private:
WORD memory[MAX_MEMORY]; // Random access memory array
WORD ip; // Instruction pointer
WORD sp; // Stack pointer
WORD fp; // Frame pointer
void systemCall(WORD n); // System call
void printState(); // Print current VM state
};
, (loadImage), (run), / (readWord, writeWord), IP, SP. printState ( ), systemCall, - ( - API).
- , , , . HALT.
void VMRuntime::run() {
WORD a, b;
WORD opcode;
ip = 0;
sp = MAX_MEMORY - 1;
while (1) {
opcode = memory[ip++];
switch (opcode) {
//------------------------------------------------------------------------
// STACK OPERATIONS
//------------------------------------------------------------------------
case OP_CONST:
memory[--sp] = memory[ip++];
break;
case OP_PUSH:
memory[--sp] = memory[memory[ip++]];
break;
case OP_POP:
memory[memory[ip++]] = memory[sp++];
break;
case OP_DUP:
a = memory[sp];
memory[--sp] = a;
break;
//------------------------------------------------------------------------
// ARITHMETIC OPERATIONS
//------------------------------------------------------------------------
case OP_INC:
memory[sp]++;
break;
case OP_DEC:
memory[sp]--;
break;
case OP_ADD:
b = memory[sp++];
a = memory[sp++];
memory[--sp] = a + b;
break;
case OP_SUB:
b = memory[sp++];
a = memory[sp++];
memory[--sp] = a - b;
break;
case OP_MUL:
b = memory[sp++];
a = memory[sp++];
memory[--sp] = a * b;
break;
case OP_DIV:
b = memory[sp++];
a = memory[sp++];
memory[--sp] = a / b;
break;
//------------------------------------------------------------------------
// BITWISE OPERATIONS
//------------------------------------------------------------------------
case OP_AND:
b = memory[sp++];
a = memory[sp++];
memory[--sp] = a & b;
break;
case OP_OR:
b = memory[sp++];
a = memory[sp++];
memory[--sp] = a | b;
break;
case OP_XOR:
b = memory[sp++];
a = memory[sp++];
memory[--sp] = a ^ b;
break;
case OP_NOT:
a = memory[sp++];
memory[--sp] = ~a;
break;
case OP_SHL:
b = memory[sp++];
a = memory[sp++];
memory[--sp] = a << b;
break;
case OP_SHR:
b = memory[sp++];
a = memory[sp++];
memory[--sp] = a >> b;
break;
//------------------------------------------------------------------------
// FLOW CONTROL OPERATIONS
//------------------------------------------------------------------------
case OP_JMP:
ip = memory[ip];
break;
case OP_CMPJE:
b = memory[sp++];
a = memory[sp++];
if (a == b) ip = memory[ip]; else ip++;
break;
case OP_CMPJNE:
b = memory[sp++];
a = memory[sp++];
if (a != b) ip = memory[ip]; else ip++;
break;
case OP_CMPJG:
b = memory[sp++];
a = memory[sp++];
if (a > b) ip = memory[ip]; else ip++;
break;
case OP_CMPJGE:
a = memory[sp++];
b = memory[sp++];
if (a >= b) ip = memory[ip]; else ip++;
break;
case OP_CMPJL:
b = memory[sp++];
a = memory[sp++];
if (a < b) ip = memory[ip]; else ip++;
break;
case OP_CMPJLE:
b = memory[sp++];
a = memory[sp++];
if (a <= b) ip = memory[ip]; else ip++;
break;
//------------------------------------------------------------------------
// PROCEDURE CALL OPERATIONS
//------------------------------------------------------------------------
case OP_CALL:
a = memory[ip++];
memory[--sp] = ip;
ip = a;
break;
case OP_RET:
ip = memory[sp++];
break;
case OP_SYSCALL:
a = memory[ip++];
systemCall(a);
break;
case OP_HALT:
printState();
return;
default:
cout << "Runtime error - unknown opcode=" << opcode << endl;
printState();
return;
}
}
// Only one system call implemented - print string (0x20)
void VMRuntime::systemCall(WORD n) {
WORD ptr;
switch (n) {
case 0x20: // print C style string
ptr = memory[sp++];
cout << ((char*)&memory[ptr]);
break;
}
}
, , , ( , ).
. , , "" .
class VMImage {
public:
VMImage();
~VMImage();
void clear();
WORD setEmitPointer(WORD address);
WORD getEmitPointer();
WORD emit(WORD opcode);
WORD emit(WORD opcode, WORD operand);
WORD readWord(WORD address);
void writeWord(WORD address, WORD value);
WORD writeData(WORD address, void* data, size_t length);
void* getImage();
size_t getImageSize();
void dissasemble();
private:
WORD memory[MAX_MEMORY];
WORD imageSize;
WORD ep;
};
, "Hello, world from VM!" 10 , , . ( , ) :
start: // [0]
push iVar // iVar
dec //
call fn // fn
dup // (Top Of Stack)
pop iVar // iVar
const 0 // 0
cmpjg start // iVar > 0 start:
halt //
fn: // [64]
const myStr //
syscall 0x20 //
ret //
dataSeg: // [128]
iVar = 10
myStr = "Hello, world from VM!\n"
仮想マシンのコマンドにすぐにコンパイルできる高級言語を作成しているので、このタスクのために仮想マシンのアセンブラ用のトランスレータを作成するのはあまりにも怠惰です。ただし、これを仮想マシンによって実行されるイメージに書き込むには、VMImage クラスを使用します。
void createExecutableImage(VMImage* img) {
WORD dataSeg = 128; // Data segment starts at 128
WORD iVar = dataSeg;
WORD myStr = dataSeg + 1;
img->writeWord(iVar, 10);
img->writeData(myStr, "Hello, world from VM!\n", 23);
WORD fn = 64;
WORD start = img->emit(OP_PUSH, iVar); // stack <- [iVar] (operand 1)
img->emit(OP_DEC); // stack[top]-- (operand 1 decrement)
img->emit(OP_CALL, fn); // Call function fn()
img->emit(OP_DUP); // duplicate stack top (operand 1 duplicate)
img->emit(OP_POP, iVar); // stack -> [iVar] (pop operand 1 duplicate to iVar)
img->emit(OP_CONST, 0); // push const 0 (operand 2)
img->emit(OP_CMPJG, start); // if (operand1 > operand2) jump to addr
img->emit(OP_HALT); // end of program
img->setEmitPointer(fn); // Function fn()
img->emit(OP_CONST, myStr); // Push constant string address
img->emit(OP_SYSCALL, 0x20); // Call system call 0x20, to print C style string to standard output
img->emit(OP_RET); // Return
}
次に、仮想マシンでイメージの実行を開始し、時間を測定します。
int main() {
VMImage* img = new VMImage();
createExecutableImage(img);
VMRuntime* vm = new VMRuntime();
vm->loadImage(img->getImage(), img->getImageSize());
auto start = std::chrono::high_resolution_clock::now();
vm->run();
auto end = std::chrono::high_resolution_clock::now();
auto ms_int = chrono::duration_cast<chrono::nanoseconds>(end - start).count();
cout << "EXECUTION TIME: " << ms_int / 1000000000.0 << "s" << endl;
delete vm;
delete img;
}
コンソールに入ります:
ほら!涼しい!スタック演算、算術演算、条件付きジャンプ命令、関数呼び出しが動作します! これは励みになります。どうもこの話は今後も展開していくらしい…