Задумал я тут ВМ написать (под скриптовый DSL для определенных нужд)
Идея простая как бревно. Есть универсальный интерфейс Value для любых значений.
Значениями могут быть числа с плавающей точкой, строки, объекты etc. (в примере только числа)
Машина предполагается регистровая. Каждая инструкция 4 байта.
Я написал простой тестовый пример. Надеюсь по коду понятно что происходит.
Это я руками сделал тупую компиляцию следующего:
Код:
x = 0
y = 1
while x <= 100000000 do
x = x + y
end
Как улучшить следующий подход?
Можно ли избежать лишней работы с кучей не прибегая к union?
Больше всего меня интересует скорость исполнения байткода. Этот пример у меня выполняется 6 сек., что слишком долго.
ps Просьба не обращать внимание на стиль, т.к. написано левой пяткой только ради эксперимента.
Код:
MODULE MyTest;
IMPORT Log := StdLog;
CONST
Add = 0; Set = 1; Get = 2; Jmp = 3; Jgt = 4; End = 5;
TYPE
Value = POINTER TO EXTENSIBLE RECORD END;
Number = POINTER TO RECORD(Value)
r: REAL;
END;
PROCEDURE (v: Value) Num(): REAL, NEW, EXTENSIBLE;
BEGIN
RETURN 0;
END Num;
PROCEDURE (v: Value) SetReal(x: REAL), NEW, EXTENSIBLE;
BEGIN
END SetReal;
PROCEDURE (v: Value) Set(x: Value), NEW, EXTENSIBLE;
BEGIN
END Set;
PROCEDURE (v: Number) Num(): REAL;
BEGIN
RETURN v.r;
END Num;
PROCEDURE (v: Number) SetReal(x: REAL);
BEGIN
v.r := x;
END SetReal;
PROCEDURE (v: Number) Set(x: Value);
BEGIN
v.r := x.Num();
END Set;
PROCEDURE emit1(b1, b2, b3, b4: INTEGER): INTEGER;
VAR w: INTEGER;
BEGIN
w := ASH(b1, 24) + ASH(b2, 16) + ASH(b3, 8) + b4;
RETURN w;
END emit1;
PROCEDURE emit2(b1, b2, b4: INTEGER): INTEGER;
VAR w: INTEGER;
BEGIN
w := ASH(b1, 24) + ASH(b2, 16) + b4;
RETURN w;
END emit2;
PROCEDURE emit3(b1, b4: INTEGER): INTEGER;
VAR w: INTEGER;
BEGIN
w := ASH(b1, 24) + b4;
RETURN w;
END emit3;
PROCEDURE emit4(b1: INTEGER): INTEGER;
VAR w: INTEGER;
BEGIN
w := ASH(b1, 24);
RETURN w;
END emit4;
PROCEDURE Do*;
VAR w: INTEGER;
b1, b2, b3, b4: INTEGER;
ip: INTEGER;
ops: ARRAY 13 OF INTEGER;
reg: ARRAY 256 OF Value;
mem: ARRAY 1000 OF Value;
r: REAL;
n: Number;
i: INTEGER;
BEGIN
(* помещение программы в память кода *)
ops[00] := emit2(Get, 0, 0); (* 0 *)
ops[01] := emit2(Set, 0, 10); (* x = 0 *)
ops[02] := emit2(Get, 0, 1); (* 1 *)
ops[03] := emit2(Set, 0, 11); (* y = 1 *)
ops[04] := emit2(Get, 0, 10); (* x *)
ops[05] := emit2(Get, 1, 2); (* 100000000 *)
ops[06] := emit3(Jgt, 11); (* if x > 100000000 goto *)
ops[07] := emit2(Get, 0, 10); (* x *)
ops[08] := emit2(Get, 1, 11); (* y *)
ops[09] := emit1(Add, 0, 0, 1); (* _ = x + y *)
ops[10] := emit2(Set, 0, 10); (* x = _ *)
ops[11] := emit3(Jmp, 3); (* goto *)
ops[12] := emit4(End); (* end *)
(* подготовка регистров *)
FOR i := 0 TO LEN(reg) - 1 DO
NEW(n); n.r := 0;
reg[i] := n;
END;
(* подготовка памяти данных *)
FOR i := 0 TO 20 DO
NEW(n); n.r := 0;
mem[i] := n;
END;
(* размещение констант в памяти *)
NEW(n); n.r := 0;
mem[0] := n;
NEW(n); n.r := 1;
mem[1] := n;
NEW(n); n.r := 100000000;
mem[2] := n;
(* цикл ВМ *)
ip := 0;
i := 0;
LOOP
w := ops[ip];
b4 := w MOD 0100H;
b3 := w DIV 0100H MOD 0100H;
b2 := w DIV 010000H MOD 0100H;
b1 := w DIV 01000000H MOD 0100H;
CASE b1 OF
|Get: reg[b2].Set(mem[b4]);
|Set: mem[b4].Set(reg[b2]);
|Add: reg[b2].SetReal(reg[b3].Num() + reg[b4].Num());
|Jmp: ip := b4;
|Jgt: IF reg[0].Num() > reg[1].Num() THEN ip := b4 END;
|End: EXIT;
END;
INC(ip);
END;
END Do;
END MyTest.Do
DevCompiler.CompileThis MyTest!!
DevDebug.UnloadThis MyTest